SvelteKit: Build Full-Stack Web Applications
Master SvelteKit for modern web development. Learn routing, server-side rendering, data loading, forms, and build performant full-stack applications.
Moshiour Rahman
Advertisement
What is SvelteKit?
SvelteKit is a full-stack framework for building web applications with Svelte. It provides routing, server-side rendering, and optimized builds out of the box.
SvelteKit Features
| Feature | Description |
|---|---|
| File-based Routing | Automatic routes from files |
| SSR/SSG | Server and static rendering |
| API Routes | Built-in backend endpoints |
| Forms | Progressive form handling |
| Streaming | Streaming SSR support |
Getting Started
Create Project
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
Project Structure
my-app/
├── src/
│ ├── routes/
│ │ ├── +page.svelte
│ │ ├── +layout.svelte
│ │ └── api/
│ ├── lib/
│ │ └── components/
│ └── app.html
├── static/
├── svelte.config.js
└── vite.config.js
Routing
Basic Pages
<!-- src/routes/+page.svelte -->
<script>
let count = 0;
</script>
<h1>Welcome to SvelteKit</h1>
<button on:click={() => count++}>
Clicked {count} times
</button>
<style>
h1 {
color: #ff3e00;
}
</style>
Dynamic Routes
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
export let data;
</script>
<article>
<h1>{data.post.title}</h1>
<p>{data.post.content}</p>
</article>
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPost(params.slug);
if (!post) {
throw error(404, 'Post not found');
}
return { post };
};
Nested Routes
routes/
├── dashboard/
│ ├── +layout.svelte # Shared layout
│ ├── +page.svelte # /dashboard
│ ├── settings/
│ │ └── +page.svelte # /dashboard/settings
│ └── profile/
│ └── +page.svelte # /dashboard/profile
<!-- src/routes/dashboard/+layout.svelte -->
<script>
import Sidebar from '$lib/components/Sidebar.svelte';
</script>
<div class="dashboard">
<Sidebar />
<main>
<slot /> <!-- Child routes render here -->
</main>
</div>
Route Groups
routes/
├── (auth)/
│ ├── login/+page.svelte
│ └── register/+page.svelte
├── (app)/
│ ├── +layout.svelte # Protected layout
│ ├── dashboard/+page.svelte
│ └── settings/+page.svelte
Data Loading
Server Load Functions
// src/routes/posts/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/db';
export const load: PageServerLoad = async ({ url, locals }) => {
const page = Number(url.searchParams.get('page')) || 1;
const limit = 10;
const posts = await db.post.findMany({
skip: (page - 1) * limit,
take: limit,
where: { published: true },
orderBy: { createdAt: 'desc' }
});
const total = await db.post.count({ where: { published: true } });
return {
posts,
pagination: {
page,
totalPages: Math.ceil(total / limit)
}
};
};
Universal Load Functions
// src/routes/posts/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch }) => {
// Runs on both server and client
const response = await fetch('/api/posts');
const posts = await response.json();
return { posts };
};
Layout Data
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.user
};
};
<!-- src/routes/+layout.svelte -->
<script>
export let data;
</script>
<nav>
{#if data.user}
<span>Welcome, {data.user.name}</span>
{:else}
<a href="/login">Login</a>
{/if}
</nav>
<slot />
Form Actions
Server Actions
// src/routes/contact/+page.server.ts
import type { Actions } from './$types';
import { fail } from '@sveltejs/kit';
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const message = data.get('message');
// Validation
if (!email || !message) {
return fail(400, {
error: 'All fields are required',
email,
message
});
}
// Process form
await sendEmail({ email, message });
return { success: true };
}
};
<!-- src/routes/contact/+page.svelte -->
<script>
import { enhance } from '$app/forms';
export let form;
</script>
{#if form?.success}
<p class="success">Message sent!</p>
{/if}
{#if form?.error}
<p class="error">{form.error}</p>
{/if}
<form method="POST" use:enhance>
<input
name="email"
type="email"
value={form?.email ?? ''}
required
/>
<textarea
name="message"
required
>{form?.message ?? ''}</textarea>
<button type="submit">Send</button>
</form>
Named Actions
// src/routes/posts/[id]/+page.server.ts
export const actions: Actions = {
publish: async ({ params }) => {
await db.post.update({
where: { id: params.id },
data: { published: true }
});
return { success: true };
},
delete: async ({ params }) => {
await db.post.delete({
where: { id: params.id }
});
throw redirect(303, '/posts');
}
};
<form method="POST" action="?/publish" use:enhance>
<button>Publish</button>
</form>
<form method="POST" action="?/delete" use:enhance>
<button>Delete</button>
</form>
API Routes
Endpoints
// src/routes/api/posts/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';
export const GET: RequestHandler = async ({ url }) => {
const limit = Number(url.searchParams.get('limit')) || 10;
const posts = await db.post.findMany({
take: limit,
where: { published: true }
});
return json(posts);
};
export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user) {
throw error(401, 'Unauthorized');
}
const data = await request.json();
const post = await db.post.create({
data: {
...data,
authorId: locals.user.id
}
});
return json(post, { status: 201 });
};
Dynamic API Routes
// src/routes/api/posts/[id]/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ params }) => {
const post = await db.post.findUnique({
where: { id: params.id }
});
if (!post) {
throw error(404, 'Post not found');
}
return json(post);
};
export const PUT: RequestHandler = async ({ params, request }) => {
const data = await request.json();
const post = await db.post.update({
where: { id: params.id },
data
});
return json(post);
};
export const DELETE: RequestHandler = async ({ params }) => {
await db.post.delete({
where: { id: params.id }
});
return new Response(null, { status: 204 });
};
Authentication
Hooks
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// Get session from cookie
const sessionId = event.cookies.get('session');
if (sessionId) {
const session = await getSession(sessionId);
if (session) {
event.locals.user = session.user;
}
}
const response = await resolve(event);
return response;
};
Protected Routes
// src/routes/(protected)/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
if (!locals.user) {
throw redirect(303, '/login');
}
return {
user: locals.user
};
};
Login/Logout
// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await authenticate(email, password);
if (!user) {
return fail(400, { error: 'Invalid credentials' });
}
const session = await createSession(user.id);
cookies.set('session', session.id, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7 // 1 week
});
throw redirect(303, '/dashboard');
}
};
State Management
Stores
// src/lib/stores/cart.ts
import { writable } from 'svelte/store';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
function createCart() {
const { subscribe, set, update } = writable<CartItem[]>([]);
return {
subscribe,
addItem: (item: CartItem) => {
update(items => {
const existing = items.find(i => i.id === item.id);
if (existing) {
existing.quantity += 1;
return items;
}
return [...items, { ...item, quantity: 1 }];
});
},
removeItem: (id: string) => {
update(items => items.filter(i => i.id !== id));
},
clear: () => set([])
};
}
export const cart = createCart();
<script>
import { cart } from '$lib/stores/cart';
$: total = $cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
</script>
<div class="cart">
{#each $cart as item}
<div>
{item.name} x {item.quantity} = ${item.price * item.quantity}
<button on:click={() => cart.removeItem(item.id)}>Remove</button>
</div>
{/each}
<p>Total: ${total}</p>
</div>
Deployment
Build Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
// import adapter from '@sveltejs/adapter-node';
// import adapter from '@sveltejs/adapter-vercel';
export default {
kit: {
adapter: adapter(),
alias: {
'$components': './src/lib/components'
}
}
};
Environment Variables
// src/routes/+page.server.ts
import { env } from '$env/dynamic/private';
import { PUBLIC_API_URL } from '$env/static/public';
export const load = async () => {
// Server-only
const apiKey = env.API_KEY;
// Public (available on client)
const apiUrl = PUBLIC_API_URL;
};
Summary
| Feature | Pattern |
|---|---|
| Pages | +page.svelte |
| Layouts | +layout.svelte |
| Server Data | +page.server.ts |
| Form Actions | actions export |
| API Routes | +server.ts |
| Hooks | hooks.server.ts |
SvelteKit provides an excellent developer experience for building performant full-stack applications.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
Turborepo: High-Performance Monorepo Build System
Master Turborepo for monorepo management. Learn workspace setup, caching, pipelines, and build performant multi-package JavaScript projects.
JavaScriptReact Hooks Complete Guide: useState to Custom Hooks
Master all React hooks from basics to advanced. Learn useState, useEffect, useContext, useReducer, useMemo, useCallback, and create custom hooks.
JavaScripttRPC: End-to-End Type-Safe APIs for TypeScript
Master tRPC for full-stack TypeScript applications. Learn procedures, routers, React Query integration, and build type-safe APIs without schemas.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.