Page Structure Standard
Overview
This document defines the standard structure and organization for pages in this Svelte application.
Core Principle: Self-Contained Svelte Files
Pages should be self-contained .svelte files containing script, styles, and markup together.
Why Self-Contained?
- ✅ Standard Svelte convention
- ✅ Co-location makes it easy to understand what belongs together
- ✅ Scoped styles prevent CSS conflicts
- ✅ Single file to edit for page changes
- ✅ TypeScript works directly in
<script lang="ts">tags - ✅ Easier to maintain for small-to-medium pages
Page File Structure
src/routes/
├── page1.svelte (self-contained: script + styles + markup)
├── page2.svelte (self-contained: script + styles + markup)
└── page3.svelte (self-contained: script + styles + markup)
Page Template
Each .svelte page file should follow this structure:
<script lang="ts">
// 1. Imports
import Card from '$lib/components/ui/card.svelte'
import Button from '$lib/components/ui/button.svelte'
// 2. Page-specific state
let count = 0
// 3. Page-specific functions
function increment() {
count++
}
// 4. Lifecycle hooks (if needed)
// onMount, onDestroy, etc.
</script>
<style>
/* Page-specific scoped styles */
/* Only use if Tailwind can't achieve the design */
/* These styles are automatically scoped to this component */
.custom-class {
/* ... */
}
</style>
<!-- 5. Markup using Tailwind + scoped styles + shadcn components -->
<div class="container mx-auto p-4">
<Card class="p-6">
<h1 class="text-2xl font-bold mb-4">Page Title</h1>
<!-- Page content here -->
</Card>
</div>
Styling Strategy
Use this hierarchy for styling (in order of preference):
-
Tailwind utility classes - Primary styling method
<div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow"> -
shadcn components - Pre-styled UI components
<Card class="p-6">
<Button>Click Me</Button>
</Card> -
Scoped
<style>tags - For styles that can't be achieved with Tailwind<style>
.custom-animation {
animation: slide 0.3s ease-in-out;
}
</style> -
Global styles - Only in
src/app.cssfor truly global styles
When to Extract Code
Extract to Separate Files When:
-
Complex business logic →
$lib/services/serviceName.ts// $lib/services/userService.ts
export function calculateUserStats(user) { ... } -
Reusable utility functions →
$lib/utils.ts// Already exists for cn() function
export function formatDate(date: Date) { ... } -
Type definitions →
$lib/types.ts// $lib/types.ts
export interface User { ... } -
API calls →
$lib/api.ts// $lib/api.ts
export async function fetchUsers() { ... } -
Shared constants →
$lib/constants.ts// $lib/constants.ts
export const API_URL = 'https://api.example.com'
Extract to Components When:
- UI element used on multiple pages
- Complex section of a page (>50 lines)
- Reusable UI patterns
Create components in $lib/components/:
src/lib/components/
├── ui/ # shadcn components
│ ├── card.svelte
│ └── button.svelte
└── Navbar.svelte # Custom shared components
Project File Organization
src/
├── routes/ # Pages (self-contained .svelte files)
│ ├── page1.svelte
│ ├── page2.svelte
│ └── page3.svelte
│
├── lib/
│ ├── components/ # Reusable components
│ │ ├── ui/ # shadcn components
│ │ │ ├── card.svelte
│ │ │ └── button.svelte
│ │ └── Navbar.svelte # Custom components
│ │
│ ├── utils.ts # Shared utility functions
│ ├── types.ts # TypeScript type definitions (when needed)
│ ├── api.ts # API calls (when needed)
│ ├── services/ # Business logic (when needed)
│ └── constants.ts # Constants (when needed)
│
├── app.css # Global styles (Tailwind + theme)
├── main.ts # App entry point
└── App.svelte # Router setup
What NOT to Do
Don't create separate .ts files for each page
Bad:
routes/
├── page1.svelte
├── page1.ts
├── page1.css
Why not:
- Adds complexity without clear benefit
- Breaks Svelte's component model
- Harder to see reactivity and component lifecycle
- More files to navigate
Good:
routes/
└── page1.svelte (everything in one file)
Don't create separate .css files for each page
Why not:
- Lose Svelte's automatic scoped styling
- Risk of CSS conflicts
- Tailwind already handles most styling needs
- Harder to see which styles apply to which markup
Don't put everything in one file forever
When a page grows too large (>200 lines):
- Extract reusable components
- Extract business logic to
$lib/services/ - Extract API calls to
$lib/api.ts - But keep the page structure in the
.sveltefile
Example: Simple Page
<h1>page1</h1>
Example: Enhanced Page
<script lang="ts">
import Card from '$lib/components/ui/card.svelte'
import Button from '$lib/components/ui/button.svelte'
let count = 0
function increment() {
count++
}
function decrement() {
count--
}
</script>
<div class="container mx-auto p-8">
<Card class="max-w-md mx-auto p-6">
<h1 class="text-3xl font-bold mb-6">Page 1</h1>
<div class="mb-4">
<p class="text-lg mb-2">Counter: <span class="font-bold">{count}</span></p>
</div>
<div class="flex gap-2">
<Button on:click={increment}>Increment</Button>
<Button on:click={decrement} class="bg-red-600 hover:bg-red-700">
Decrement
</Button>
</div>
</Card>
</div>
Example: Page with Extracted Logic
When business logic gets complex, extract it:
page2.svelte:
<script lang="ts">
import Card from '$lib/components/ui/card.svelte'
import { calculateStats } from '$lib/services/statsService'
let data = calculateStats()
</script>
<Card class="p-6">
<h1>Page 2</h1>
<p>Result: {data.result}</p>
</Card>
$lib/services/statsService.ts:
export function calculateStats() {
// Complex calculation logic here
return { result: 42 }
}
Benefits of This Approach
✅ Simple - One file per page, easy to find everything ✅ Standard - Follows Svelte best practices ✅ Scoped - Styles don't leak between components ✅ Maintainable - Clear boundaries, easy to refactor ✅ Scalable - Can extract pieces as complexity grows ✅ TypeScript-friendly - Full type support in script tags ✅ Co-located - Related code lives together
Summary
- Pages are self-contained
.sveltefiles - Use Tailwind for styling first
- Extract logic when it gets complex or is reused
- Extract components when UI is reused
- Keep it simple until you need complexity