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-appTry it Yourself »
Exercise
A page route file is named…
src/routes/about/
.svelte
Starts with +.
Discussion
Loading…