Layout Groups och Avancerad Routing 🏗️
Ditt uppdrag 🎯
Organisera din app med avancerade layout-tekniker:
- Skapa olika layouts för inloggade vs utloggade användare
- Förstå layout-hierarkier och inheritance
- Implementera shared data genom layout.server.ts
- Bygga en professionell app-struktur
Mål: En app som känns som en riktig produkt med konsekvent design och smart organisering.
Hittills har du byggt funktionalitet. Nu lär vi oss att organisera den som en riktig app med professionell struktur och användargränssnitt!
Fas 1: Förstå layout-systemet 🧠
Steg 1: Vad är layouts egentligen?
Layouts = delad kod mellan sidor
Utan layouts:
<!-- /login/+page.svelte -->
<nav>...</nav>
<main>Login content</main>
<footer>...</footer>
<!-- /dashboard/+page.svelte -->
<nav>...</nav>
<main>Dashboard content</main>
<footer>...</footer>
Med layouts:
<!-- +layout.svelte -->
<nav>...</nav>
<main><slot /></main>
<footer>...</footer>
<!-- /login/+page.svelte -->
<h1>Login</h1>
<form>...</form>
<!-- /dashboard/+page.svelte -->
<h1>Dashboard</h1>
<div>...</div>
Frågor att reflektera:
- Vad händer när du vill ändra navigation på hela siten?
- Hur kan du dela data mellan alla sidor?
- Vad händer om vissa sidor behöver olika layouts?
Steg 2: Layout-hierarkier
SvelteKit layouts fungerar hierarkiskt:
src/routes/
├── +layout.svelte # Alla sidor
├── +layout.server.ts # Data för alla sidor
├── dashboard/
│ ├── +layout.svelte # Alla dashboard-sidor
│ ├── +layout.server.ts # Data för dashboard-sidor
│ ├── +page.svelte # /dashboard
│ └── profile/
│ └── +page.svelte # /dashboard/profile
└── login/
└── +page.svelte # /login
Hur fungerar arv:
/dashboard/profile
får layout från BÅDE root OCH dashboard/login
får bara root layout- Data från parent layouts är tillgängligt i child layouts
Steg 3: Layout Groups - den magiska funktionen
Problem: Olika sidor behöver helt olika layouts
Exempel-scenario:
- Inloggade användare: Navigation, sidebar, notifikationer
- Utloggade användare: Minimal design, fokus på login/register
- Admin-sidor: Speciell admin-navigation
Layout Groups löser detta:
src/routes/
├── +layout.svelte # Grundlayout för alla
├── (authenticated)/
│ ├── +layout.svelte # Layout för inloggade
│ ├── +layout.server.ts # Auth-krav
│ ├── dashboard/+page.svelte # /dashboard
│ └── profile/+page.svelte # /profile
└── (public)/
├── +layout.svelte # Layout för publika sidor
├── login/+page.svelte # /login
└── register/+page.svelte # /register
Viktigt: Parenteser ()
gör att gruppnamnet INTE syns i URL:en!
Fas 2: Planera din app-struktur 📋
Steg 1: Identifiera dina layout-behov
Fundera: Vilka olika "typer" av sidor har din app?
Vanliga kategorier:
- Public pages: Landing, login, register, about
- Authenticated pages: Dashboard, profile, settings
- Special pages: Admin, error pages, maintenance
För varje kategori, tänk på:
- Vilken navigation behövs?
- Vilka komponenter ska vara synliga? (user menu, notifications, etc.)
- Vilken data behövs på alla sidor i gruppen?
- Vilken styling/tema ska användas?
Steg 2: Designa din mapstruktur
Din uppgift: Planera hur du vill organisera din app
src/routes/
├── +layout.svelte # Global layout (basic HTML)
├── +layout.server.ts # Global data (tema, språk, etc.)
├── (public)/
│ ├── +layout.svelte # ??? vad ska finnas här?
│ ├── +page.svelte # Landing page (/)
│ ├── login/+page.svelte # /login
│ └── register/+page.svelte # /register
└── (authenticated)/
├── +layout.svelte # ??? vad ska finnas här?
├── +layout.server.ts # ??? vilken data?
├── dashboard/+page.svelte # /dashboard
├── profile/+page.svelte # /profile
└── settings/+page.svelte # /settings
Frågor att besvara:
- Vad ska vara gemensamt för alla publika sidor?
- Vad ska vara gemensamt för alla inloggade sidor?
- Vilken data behöver laddas för inloggade användare?
Steg 3: Tänk på data-flöden
Layout.server.ts kan ladda data för alla child-sidor:
// /+layout.server.ts (global)
export const load = async ({ cookies }) => {
return {
theme: cookies.get('theme') || 'light',
// Global data för alla sidor
};
};
// /(authenticated)/+layout.server.ts
export const load = async ({ cookies, parent }) => {
const { theme } = await parent(); // Få data från parent layout
const user = // ... hämta inloggad användare
return {
user,
// Data för alla autentiserade sidor
};
};
Viktiga frågor:
- Vilken data behöver ALLA sidor? (global layout)
- Vilken data behöver bara inloggade sidor?
- Hur hanterar du loading states och errors?
Fas 3: Implementera layout groups 🔧
Steg 1: Skapa grundstruktur
Skapa din mappstruktur:
# Skapa layout groups (kom ihåg parenteserna!)
mkdir src/routes/\(public\)
mkdir src/routes/\(authenticated\)
# Flytta befintliga sidor
mv src/routes/login src/routes/\(public\)/
mv src/routes/register src/routes/\(public\)/
mv src/routes/dashboard src/routes/\(authenticated\)/
# ... flytta andra sidor till rätt grupp
Testa: Fungerar dina URLs fortfarande? /login
ska fortfarande fungera!
Steg 2: Global layout
Skapa/uppdatera src/routes/+layout.svelte
:
<script>
export let data;
// Vilken global data vill du ha tillgänglig överallt?
</script>
<!DOCTYPE html>
<html lang="en" data-theme={data?.theme || 'light'}>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div id="app">
<slot />
</div>
<!-- Global styles, scripts, etc. -->
%sveltekit.body%
</body>
</html>
<style>
/* Global CSS som gäller hela appen */
:global(body) {
margin: 0;
font-family: system-ui, sans-serif;
}
/* Tema-variabler */
:global([data-theme="light"]) {
--bg-color: white;
--text-color: black;
}
:global([data-theme="dark"]) {
--bg-color: black;
--text-color: white;
}
</style>
Steg 3: Public layout
Skapa src/routes/(public)/+layout.svelte
:
<script>
export let data;
// Data från parent + egen data
</script>
<div class="public-layout">
<header class="public-header">
<nav>
<a href="/">Home</a>
<a href="/login">Login</a>
<a href="/register">Register</a>
</nav>
</header>
<main class="public-main">
<slot />
</main>
<footer class="public-footer">
<!-- Din footer för publika sidor -->
</footer>
</div>
<style>
.public-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.public-header {
/* Din styling för public header */
}
.public-main {
flex: 1;
/* Content area styling */
}
.public-footer {
/* Footer styling */
}
</style>
Steg 4: Authenticated layout
Skapa src/routes/(authenticated)/+layout.svelte
:
<script>
export let data;
// Här har du tillgång till user-data från +layout.server.ts
</script>
<div class="auth-layout">
<header class="auth-header">
<nav class="main-nav">
<a href="/dashboard">Dashboard</a>
<a href="/profile">Profile</a>
<a href="/settings">Settings</a>
</nav>
<div class="user-menu">
<span>Welcome, {data.user?.username}!</span>
<form method="POST" action="/login?/logout">
<button type="submit">Logout</button>
</form>
</div>
</header>
<div class="content-area">
<aside class="sidebar">
<!-- Din sidebar för inloggade användare -->
<nav class="sidebar-nav">
<!-- Navigation, shortcuts, etc. -->
</nav>
</aside>
<main class="main-content">
<slot />
</main>
</div>
</div>
<style>
.auth-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.auth-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #eee;
}
.content-area {
flex: 1;
display: flex;
}
.sidebar {
width: 250px;
background: #f5f5f5;
padding: 1rem;
}
.main-content {
flex: 1;
padding: 2rem;
}
</style>
Fas 4: Implementera layout.server.ts 📊
Steg 1: Authenticated layout server
Skapa src/routes/(authenticated)/+layout.server.ts
:
import type { LayoutServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { prisma } from '$lib';
export const load = (async ({ cookies, parent }) => {
// Få data från parent layout
const parentData = await parent();
// Kontrollera auth
const userId = cookies.get('userId');
if (!userId) {
throw redirect(307, '/login');
}
// Hämta user-data
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
username: true,
// Vilka andra fält vill du ha tillgängliga på alla autentiserade sidor?
}
});
if (!user) {
// Invalid session
cookies.delete('userId', { path: '/' });
throw redirect(307, '/login');
}
// Hämta annan data som alla autentiserade sidor behöver
const notifications = // ... hämta notifications för användaren
const unreadCount = // ... antal olästa meddelanden
return {
user,
notifications,
unreadCount,
// Denna data är tillgänglig på alla /(authenticated) sidor
};
}) satisfies LayoutServerLoad;
Steg 2: Global layout server (valfritt)
Skapa src/routes/+layout.server.ts
:
import type { LayoutServerLoad } from './$types';
export const load = (async ({ cookies, url }) => {
return {
theme: cookies.get('theme') || 'light',
currentPath: url.pathname,
// Global data för alla sidor
};
}) satisfies LayoutServerLoad;
Steg 3: Använd data i dina komponenter
I vilken /(authenticated) sida som helst:
<script>
export let data;
// data innehåller user, notifications, etc. från layout.server.ts
</script>
<h1>Welcome to Dashboard, {data.user.username}!</h1>
{#if data.unreadCount > 0}
<div class="notification">
You have {data.unreadCount} unread notifications
</div>
{/if}
<!-- Resten av din sida -->
Fas 5: Avancerade layout-tekniker 🎯
Steg 1: Conditional layouts
Olika layouts baserat på villkor:
<!-- /(authenticated)/+layout.svelte -->
<script>
export let data;
// Olika layout baserat på user-rolle
$: isAdmin = data.user?.role === 'admin';
$: isMobile = typeof window !== 'undefined' && window.innerWidth < 768;
</script>
{#if isAdmin}
<div class="admin-layout">
<!-- Special admin navigation -->
<slot />
</div>
{:else if isMobile}
<div class="mobile-layout">
<!-- Mobile-optimized layout -->
<slot />
</div>
{:else}
<div class="standard-layout">
<!-- Standard desktop layout -->
<slot />
</div>
{/if}
Steg 2: Layout data inheritance
Kombinera data från flera layout-nivåer:
// /(authenticated)/dashboard/+layout.server.ts
export const load = async ({ parent }) => {
const { user } = await parent(); // Från authenticated layout
// Dashboard-specifik data
const dashboardStats = await getDashboardStats(user.id);
return {
dashboardStats
// Nu har alla /dashboard/* sidor både user OCH dashboardStats
};
};
Steg 3: Layout component reuse
Skapa återanvändbara layout-komponenter:
<!-- src/lib/components/AuthenticatedLayout.svelte -->
<script>
export let user;
export let showSidebar = true;
</script>
<div class="layout">
<header>
<!-- Navigation component -->
</header>
<div class="content">
{#if showSidebar}
<aside>
<!-- Sidebar component -->
</aside>
{/if}
<main class:full-width={!showSidebar}>
<slot />
</main>
</div>
</div>
Använd i olika layouts:
<!-- /(authenticated)/+layout.svelte -->
<script>
import AuthenticatedLayout from '$lib/components/AuthenticatedLayout.svelte';
export let data;
</script>
<AuthenticatedLayout user={data.user}>
<slot />
</AuthenticatedLayout>
Fas 6: UX-förbättringar och polish 💫
Steg 1: Loading states
Hantera loading mellan sidor:
<!-- +layout.svelte -->
<script>
import { navigating } from '$app/stores';
export let data;
</script>
{#if $navigating}
<div class="loading-overlay">
<div class="spinner">Loading...</div>
</div>
{/if}
<slot />
<style>
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
</style>
Steg 2: Breadcrumbs och navigation
Automatiska breadcrumbs baserat på route:
<!-- /(authenticated)/+layout.svelte -->
<script>
import { page } from '$app/stores';
$: breadcrumbs = generateBreadcrumbs($page.url.pathname);
function generateBreadcrumbs(pathname) {
// Implementera logik för att skapa breadcrumbs
// /dashboard/profile/settings → [Dashboard, Profile, Settings]
}
</script>
<nav class="breadcrumbs">
{#each breadcrumbs as crumb, i}
{#if i < breadcrumbs.length - 1}
<a href={crumb.href}>{crumb.label}</a>
<span class="separator">›</span>
{:else}
<span class="current">{crumb.label}</span>
{/if}
{/each}
</nav>
Steg 3: Theme switching
Implementera tema-växling:
<!-- Global layout component -->
<script>
import { browser } from '$app/environment';
export let data;
let theme = data.theme;
function toggleTheme() {
theme = theme === 'light' ? 'dark' : 'light';
if (browser) {
// Spara i cookie
document.cookie = `theme=${theme}; path=/; max-age=${60 * 60 * 24 * 365}`;
// Uppdatera DOM
document.documentElement.setAttribute('data-theme', theme);
}
}
</script>
<button on:click={toggleTheme} class="theme-toggle">
{theme === 'light' ? '🌙' : '☀️'}
</button>
Utmaningar att lösa själv 🎯
Grundnivå:
- Implementera layout groups för public vs authenticated ✓
- Shared authentication logic i layout.server.ts ✓
- Konsekvent navigation för varje group ✓
Mellannivå:
- Nested layouts för sub-sektioner (admin, dashboard modules)
- Responsive layouts som anpassar sig till skärmstorlek
- Breadcrumb navigation för djupa routes
Expertnivå:
- Dynamic layouts baserat på user permissions
- Layout animations och transitions
- Advanced data prefetching i layout hierarkier
Best Practices för layouts 📝
✅ Performance:
- Ladda bara nödvändig data i varje layout-nivå
- Använd
parent()
för att undvika dublicering - Cacha tunga beräkningar
✅ Användarupplevelse:
- Konsekvent navigation mellan related sidor
- Tydliga loading states
- Breadcrumbs för djupa hierarkier
✅ Kod-organisation:
- Dela återanvändbara layout-komponenter
- Tydliga naming conventions för layout groups
- Dokumentera layout-hierarkier
Vanliga fallgropar ⚠️
❌ För många layout-nivåer
src/routes/
├── (authenticated)/
│ └── dashboard/
│ └── (premium)/
│ └── analytics/
│ └── (advanced)/
│ └── +page.svelte # För djupt!
❌ Dublicerad data-loading
// DÅLIGT - laddar user data överallt
// /(authenticated)/+layout.server.ts
export const load = async ({ cookies }) => {
const user = await getUser(cookies); // ❌
return { user };
};
// /(authenticated)/dashboard/+layout.server.ts
export const load = async ({ cookies }) => {
const user = await getUser(cookies); // ❌ Dubbelt arbete!
return { user };
};
// BÄTTRE - använd parent()
export const load = async ({ parent }) => {
const { user } = await parent(); // ✅
return { user };
};
❌ Layout groups utan mening
(group1)/ och (group2)/ som har identiska layouts
Reflektion och lärande 🤔
Efter implementation, reflektera:
Struktur-förståelse:
- Hur förändrade layout groups din app-organisation?
- Vilka fördelar ser du med att dela data via layouts?
- Vad skulle du strukturera annorlunda nästa gång?
Användarupplevelse:
- Känns din app mer professionell nu?
- Hur förbättrades navigation och flöde?
- Vilka UX-förbättringar vill du lägga till?
Code-kvalitet:
- Blev din kod mer DRY (Don't Repeat Yourself)?
- Hur påverkades maintainability?
- Vilka patterns ser du som återanvändbara?
Grattis! 🎉
Du har nu byggt en professionell webbapp med:
- ✅ Säker autentisering med krypterade lösenord
- ✅ Komplex databas med relationer
- ✅ Filuppladdning och media-hantering
- ✅ Professionell layout med smart organisering
- ✅ Production deployment på riktigt hosting
- ✅ Fullstack kompetens från frontend till backend
Detta är nivån som många professionella utvecklare arbetar på dagligen!
Med dessa kunskaper kan du bygga och deploya professionella webbapplikationer. Du förstår hela stacken och kan arbeta självständigt på riktiga projekt!
Steg 4: Layout Groups - Praktiskt tillägg
## Fas 6: Professionell organisation av din app 🏗️
### Organisera din auth-app med layout groups
Ta samma app och gör den riktig professionell:
### Layout-strategi baserat på din app:
**Forum-app:**
```markdown
(public)/
├── +layout.svelte # Enkel navbar: Home, Login, Register
├── +page.svelte # Landing page
└── login/+page.svelte
(authenticated)/
├── +layout.svelte # Full navbar: Forums, Profile, Logout
├── forums/ # Alla forum-sidor
└── profile/+page.svelte
Market-app:
(public)/
├── browse/+page.svelte # Visa items utan att kunna köpa
└── login/+page.svelte
(authenticated)/
├── market/ # Skapa items, hantera bids
├── my-items/+page.svelte # Mina items och mina bids
└── profile/+page.svelte
Praktisk implementation:
- Analysera din app - vilka sidor behöver auth?
- Skapa layout groups enligt ovan mönster
- Flytta sidor till rätt grupp
- Olika navigation för public vs authenticated
- Använd layout.server.ts för auth-kontroll
Resultat:
Din app ska kännas som en riktig produkt med tydlig separation mellan public och private delar!
Resurser för fördjupning 📚
- SvelteKit Advanced Routing: kit.svelte.dev/docs/advanced-routing
- Layout Documentation: kit.svelte.dev/docs/routing#layout
- SvelteKit Best Practices: github.com/sveltejs/kit/discussions
- Component Design Systems: storybook.js.org