iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

SvelteKit Intro

SvelteKit is the Svelte framework: file-based routing, server endpoints, server-side rendering, deployment adapters for Vercel/Netlify/Cloudflare/Node. Pages are +page.svelte, data loads via +page.server.ts, forms post to action endpoints. Modern, fast, opinionated.

Routing, load, actions, adapters

EXAMPLE
// 1) Install
// npm create svelte@latest my-app
// cd my-app
// npm install
// npm run dev

// 2) File-based routing
// src/routes/
//   +page.svelte                    → /
//   about/+page.svelte               → /about
//   blog/+page.svelte                → /blog
//   blog/[slug]/+page.svelte         → /blog/:slug
//   blog/[slug]/+page.server.ts      → server-only data loader
//   api/posts/+server.ts             → /api/posts (HTTP endpoint)

// Special files:
//   +layout.svelte       — wraps child pages
//   +layout.server.ts    — server data for layout
//   +page.svelte         — page UI
//   +page.ts             — universal data loader (server + client)
//   +page.server.ts      — server-only data + form actions
//   +error.svelte        — error page
//   +server.ts           — HTTP endpoint (GET/POST/...)

// 3) Loading data — server-side
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';

export const load: PageServerLoad = async ({ params, locals }) => {
    const post = await locals.db.post.findUnique({ where: { slug: params.slug } });
    if (!post) throw error(404, 'Not found');
    return { post };
};

// src/routes/blog/[slug]/+page.svelte
<script lang="ts">
    import type { PageData } from './$types';
    export let data: PageData;       // typed automatically
</script>
<h1>{data.post.title}</h1>
<p>{data.post.body}</p>

// 4) Universal load — runs on server (SSR) AND client (CSR navigation)
// +page.ts
export const load: PageLoad = async ({ params, fetch }) => {
    const r = await fetch(`/api/posts/${params.slug}`);
    return { post: await r.json() };
};

// fetch is patched for SSR — same URL works server-side and client-side.

// 5) Layouts
// src/routes/+layout.svelte
<script>
    import '../app.css';
    import Nav from './Nav.svelte';
</script>
<Nav />
<main><slot /></main>

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
    return { user: locals.user };
};

// Children inherit via $page.data.user.

// 6) Form actions
// src/routes/login/+page.server.ts
import type { Actions } from './$types';
import { fail, redirect } from '@sveltejs/kit';

export const actions: Actions = {
    default: async ({ request, cookies }) => {
        const data = await request.formData();
        const email = data.get('email')?.toString() ?? '';
        const pw    = data.get('password')?.toString() ?? '';
        const user = await auth.verify(email, pw);
        if (!user) return fail(401, { error: 'Invalid credentials' });
        cookies.set('session', user.token, { path: '/', httpOnly: true, secure: true, sameSite: 'lax' });
        throw redirect(303, '/dashboard');
    },
};

// +page.svelte
<form method="POST">
    <input name="email" type="email" required>
    <input name="password" type="password" required>
    <button>Sign in</button>
</form>

// Progressive enhancement: works without JS; SvelteKit hydrates and uses fetch.

// 7) HTTP endpoints
// src/routes/api/posts/+server.ts
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ url, locals }) => {
    const limit = Number(url.searchParams.get('limit') ?? 20);
    const posts = await locals.db.post.findMany({ take: limit });
    return json(posts);
};

export const POST: RequestHandler = async ({ request, locals }) => {
    if (!locals.user) throw error(401);
    const body = await request.json();
    const post = await locals.db.post.create({ data: body });
    return json(post, { status: 201 });
};

// 8) hooks.server.ts — runs on every request
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
    const sid = event.cookies.get('session');
    if (sid) event.locals.user = await sessions.get(sid);
    return resolve(event);
};

// 9) Stores + state
// src/lib/stores.ts
import { writable } from 'svelte/store';
export const cart = writable<CartItem[]>([]);

// Use anywhere: import { cart } from '$lib/stores'; $cart.push(...)
// SSR: writable created per request via context to avoid sharing state across requests.

// 10) Deployment — adapters
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
// import adapter from '@sveltejs/adapter-netlify';
// import adapter from '@sveltejs/adapter-cloudflare';
// import adapter from '@sveltejs/adapter-node';
// import adapter from '@sveltejs/adapter-static';

export default { kit: { adapter: adapter() } };

// 11) Environment variables
// .env
DATABASE_URL=postgresql://...
STRIPE_KEY=sk_...

// Access
import { env } from '$env/dynamic/private';     // server-only
import { env as pubEnv } from '$env/dynamic/public'; // PUBLIC_-prefixed vars exposed to client

console.log(env.DATABASE_URL);
console.log(pubEnv.PUBLIC_API_URL);

// 12) Error handling
// src/routes/+error.svelte
<script>
    import { page } from '$app/stores';
</script>
<h1>{$page.status} — {$page.error?.message}</h1>

// 13) Loading indicators + transitions
<script>
    import { navigating } from '$app/stores';
</script>
{#if $navigating}
    <progress />
{/if}

// 14) Common bugs
// • Importing client-only code in +page.server.ts → runtime error (browser APIs unavailable)
// • Calling server-only env in +page.ts → leaks secrets to client; use '$env/dynamic/private' in server files
// • Not handling redirect correctly — throw redirect(303, '/path'), don't return
// • Forgetting types from './$types' — generated per route; restart dev server if missing
// • Mixing form actions with body parsing — actions use formData(), not JSON
// • Stores in SSR with global state → bleed across requests; create per-request in hooks
// • Cache-Control on dynamic pages → stale content; set per-route in load
// • Forgetting load() runs on every navigation — heavy queries hurt UX; cache or paginate
// • Adapter mismatch — Vercel adapter on Cloudflare → errors; pick the right one
// • CSR navigation but +page.server.ts data needed — use 'load' with fetch instead of direct DB

Why it matters

SvelteKit gives you file-based routing, server endpoints, form actions, SSR, and deployment adapters out of the box. Use +page.server.ts for server-only data, +page.ts for universal data, +server.ts for HTTP endpoints, and form actions for progressive enhancement. Hook globally via hooks.server.ts and pick the adapter that matches your host.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Full framework around Svelte — routing, SSR, deploy adapters.
npm create svelte@latest my-app
Try it Yourself »

Exercise

A page route file is named…

src/routes/about/ .svelte

Discussion

Loading…