Hoppa till huvudinnehåll

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.

Från basic till professionell! ✨

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

  1. Implementera layout groups för public vs authenticated ✓
  2. Shared authentication logic i layout.server.ts ✓
  3. Konsekvent navigation för varje group ✓

Mellannivå:

  1. Nested layouts för sub-sektioner (admin, dashboard modules)
  2. Responsive layouts som anpassar sig till skärmstorlek
  3. Breadcrumb navigation för djupa routes

Expertnivå:

  1. Dynamic layouts baserat på user permissions
  2. Layout animations och transitions
  3. 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!

Du är nu en fullstack-utvecklare! 💪

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:

  1. Analysera din app - vilka sidor behöver auth?
  2. Skapa layout groups enligt ovan mönster
  3. Flytta sidor till rätt grupp
  4. Olika navigation för public vs authenticated
  5. 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 📚