Skip to main content

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):

  1. Tailwind utility classes - Primary styling method

    <div class="flex items-center gap-4 p-6 bg-white rounded-lg shadow">
  2. shadcn components - Pre-styled UI components

    <Card class="p-6">
    <Button>Click Me</Button>
    </Card>
  3. Scoped <style> tags - For styles that can't be achieved with Tailwind

    <style>
    .custom-animation {
    animation: slide 0.3s ease-in-out;
    }
    </style>
  4. Global styles - Only in src/app.css for 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 .svelte file

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 .svelte files
  • 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