SvelteKit
Official full-stack framework for Svelte. Achieves high performance and small bundle sizes through compile-time optimization. Rapidly growing attention in 2025.
GitHub Overview
sveltejs/kit
web development, streamlined
Topics
Star History
Framework
SvelteKit
Overview
SvelteKit is a full-stack framework for building web applications using Svelte as the component language. It provides features like file-based routing, SSR, SSG, form actions, and API routes.
Details
SvelteKit is a meta-framework for Svelte developed by the Svelte team in 2021, positioned as "the fastest way to build Svelte apps." It leverages Svelte's lightweight nature and performance while integrating features necessary for modern web application development. Key features include a file-based routing system (automatic route generation via src/routes structure), server-side rendering (SSR) with client-side hydration, static site generation (SSG) support, progressively enhanced form actions, API endpoint creation capabilities, complete TypeScript support, Vite-based fast development environment, and an adapter-based flexible deployment system. SvelteKit supports a wide range of use cases including hybrid rendering (SSR + CSR), fully static sites, SPAs, serverless applications, mobile apps (Tauri/Capacitor), and PWAs, with flexible deployment to Vercel, Netlify, Cloudflare Pages, AWS Lambda, Node.js, and more.
Pros and Cons
Pros
- File-based Routing: Intuitive automatic route generation via src/routes structure
- Flexible Rendering: Choose between SSR, SSG, SPA, and hybrid rendering
- High Performance: Benefits from Svelte's compile-time optimization
- Progressive Enhancement: Forms work without JavaScript
- Complete TypeScript Support: Auto-generated type definitions and type safety
- Vite-based: Fast HMR and development experience
- Adapter System: Support for 50+ deployment targets
- Full-stack Features: API routes, data loading, form actions
Cons
- Learning Curve: Need to understand Svelte + SvelteKit-specific concepts
- Ecosystem: Fewer libraries compared to React/Vue.js
- Community Size: Smaller compared to other meta-frameworks
- Tooling Maturity: Development tools and integration solutions are still evolving
- Documentation Volume: Limited Japanese learning resources
- Enterprise Adoption: Fewer enterprise adoption cases
- Third-party Integration: Some libraries have complex integration
Key Links
- SvelteKit Official Website
- SvelteKit Official Documentation
- SvelteKit GitHub Repository
- SvelteKit Adapters
- SvelteKit Community
- SvelteKit Tutorial
Code Examples
Hello World
<!-- src/routes/+page.svelte -->
<script>
// Receiving data
/** @type {import('./$types').PageData} */
export let data;
// Local state
let count = 0;
let name = '';
// Reactive statements
$: greeting = name ? `Hello, ${name}!` : 'Hello!';
function increment() {
count += 1;
}
</script>
<main>
<h1>Welcome to SvelteKit!</h1>
<div class="greeting-section">
<p>{greeting}</p>
<input
bind:value={name}
placeholder="Enter your name"
/>
</div>
<div class="counter-section">
<p>Count: {count}</p>
<button on:click={increment}>+1</button>
</div>
{#if data}
<div class="data-section">
<h2>Server Data</h2>
<p>Current Time: {data.timestamp}</p>
<p>Environment: {data.environment}</p>
</div>
{/if}
</main>
<style>
main {
text-align: center;
padding: 1em;
max-width: 240px;
margin: 0 auto;
}
.greeting-section, .counter-section, .data-section {
margin: 2em 0;
padding: 1em;
border: 1px solid #ddd;
border-radius: 8px;
}
input {
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
margin-top: 0.5em;
}
button {
background: #ff3e00;
color: white;
border: none;
padding: 0.5em 1em;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
}
button:hover {
background: #cc3200;
}
h1 {
color: #ff3e00;
text-transform: uppercase;
font-size: 2em;
font-weight: 100;
}
</style>
// src/routes/+page.js
/** @type {import('./$types').PageLoad} */
export function load() {
return {
timestamp: new Date().toLocaleString('en-US'),
environment: 'development'
};
}
File-based Routing and Layouts
<!-- src/routes/+layout.svelte -->
<script>
import { page } from '$app/stores';
import { navigating } from '$app/stores';
// Receiving layout data
/** @type {import('./$types').LayoutData} */
export let data;
</script>
<div class="app">
<header>
<nav>
<a href="/" class:active={$page.url.pathname === '/'}>
Home
</a>
<a href="/about" class:active={$page.url.pathname === '/about'}>
About
</a>
<a href="/blog" class:active={$page.url.pathname.startsWith('/blog')}>
Blog
</a>
<a href="/api/users" class:active={$page.url.pathname.startsWith('/api')}>
API
</a>
</nav>
{#if $navigating}
<div class="loading">Loading...</div>
{/if}
</header>
<main>
<!-- Child page content renders here -->
<slot />
</main>
<footer>
<p>© 2024 SvelteKit Application</p>
{#if data.user}
<p>Logged in as: {data.user.name}</p>
{/if}
</footer>
</div>
<style>
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
header {
padding: 1rem;
background: #282c34;
color: white;
}
nav {
display: flex;
gap: 1rem;
}
nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background 0.2s;
}
nav a:hover {
background: rgba(255, 255, 255, 0.1);
}
nav a.active {
background: #ff3e00;
}
.loading {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
background: #ff3e00;
color: white;
padding: 0.5rem 1rem;
border-radius: 0 0 4px 4px;
z-index: 1000;
}
main {
flex: 1;
padding: 2rem;
}
footer {
padding: 1rem;
background: #f5f5f5;
text-align: center;
color: #666;
}
</style>
// src/routes/+layout.js
/** @type {import('./$types').LayoutLoad} */
export async function load({ fetch, url }) {
// Fetch layout common data
const user = await fetch('/api/me').then(r => r.ok ? r.json() : null);
return {
user,
currentPath: url.pathname
};
}
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
<svelte:head>
<title>{data.post.title} - Blog</title>
<meta name="description" content={data.post.excerpt} />
</svelte:head>
<article>
<header>
<h1>{data.post.title}</h1>
<p class="meta">
Published: {new Date(data.post.date).toLocaleDateString('en-US')}
・Category: {data.post.category}
</p>
</header>
<div class="content">
{@html data.post.content}
</div>
<footer>
<a href="/blog">← Back to Blog</a>
</footer>
</article>
<style>
article {
max-width: 800px;
margin: 0 auto;
}
header h1 {
color: #333;
margin-bottom: 0.5rem;
}
.meta {
color: #666;
font-size: 0.9rem;
margin-bottom: 2rem;
}
.content {
line-height: 1.6;
margin-bottom: 2rem;
}
footer a {
color: #ff3e00;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
</style>
Data Loading and Server-side Features
// src/routes/blog/[slug]/+page.server.js
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params, fetch }) {
try {
// Fetch blog post from database or API
const response = await fetch(`/api/posts/${params.slug}`);
if (!response.ok) {
if (response.status === 404) {
throw error(404, 'Post not found');
}
throw error(500, 'Server error occurred');
}
const post = await response.json();
return {
post: {
title: post.title,
content: post.content,
excerpt: post.excerpt,
date: post.publishedAt,
category: post.category,
author: post.author
}
};
} catch (err) {
console.error('Blog post loading error:', err);
throw error(500, 'Failed to load blog post');
}
}
// src/routes/api/posts/+server.js
import { json } from '@sveltejs/kit';
// Mock data (would normally fetch from database)
const posts = [
{
id: '1',
slug: 'sveltekit-introduction',
title: 'Introduction to SvelteKit',
content: '<p>Learn the basics of SvelteKit...</p>',
excerpt: 'Basic usage of SvelteKit',
publishedAt: '2024-01-15T10:00:00Z',
category: 'Technology',
author: { name: 'John Doe', email: '[email protected]' }
},
{
id: '2',
slug: 'sveltekit-routing',
title: 'SvelteKit Routing',
content: '<p>About file-based routing...</p>',
excerpt: 'Details on file-based routing',
publishedAt: '2024-01-20T14:30:00Z',
category: 'Technology',
author: { name: 'Jane Smith', email: '[email protected]' }
}
];
/** @type {import('./$types').RequestHandler} */
export async function GET({ url }) {
const page = parseInt(url.searchParams.get('page') ?? '1');
const limit = parseInt(url.searchParams.get('limit') ?? '10');
const category = url.searchParams.get('category');
let filteredPosts = posts;
// Category filtering
if (category) {
filteredPosts = posts.filter(post =>
post.category.toLowerCase() === category.toLowerCase()
);
}
// Pagination
const start = (page - 1) * limit;
const end = start + limit;
const paginatedPosts = filteredPosts.slice(start, end);
return json({
posts: paginatedPosts,
pagination: {
page,
limit,
total: filteredPosts.length,
totalPages: Math.ceil(filteredPosts.length / limit)
}
});
}
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
try {
const data = await request.json();
// Validation
if (!data.title || !data.content) {
return json(
{ error: 'Title and content are required' },
{ status: 400 }
);
}
// Create new post (would normally save to database)
const newPost = {
id: String(posts.length + 1),
slug: data.title.toLowerCase().replace(/\s+/g, '-'),
title: data.title,
content: data.content,
excerpt: data.excerpt || data.content.substring(0, 100) + '...',
publishedAt: new Date().toISOString(),
category: data.category || 'Uncategorized',
author: data.author
};
posts.push(newPost);
return json(newPost, { status: 201 });
} catch (error) {
console.error('Post creation error:', error);
return json(
{ error: 'Failed to create post' },
{ status: 500 }
);
}
}
// src/routes/api/posts/[id]/+server.js
import { json, error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function GET({ params }) {
// Would normally fetch from database
const post = posts.find(p => p.id === params.id || p.slug === params.id);
if (!post) {
throw error(404, 'Post not found');
}
return json(post);
}
/** @type {import('./$types').RequestHandler} */
export async function PUT({ params, request }) {
const postIndex = posts.findIndex(p => p.id === params.id);
if (postIndex === -1) {
throw error(404, 'Post not found');
}
const updates = await request.json();
posts[postIndex] = { ...posts[postIndex], ...updates };
return json(posts[postIndex]);
}
/** @type {import('./$types').RequestHandler} */
export async function DELETE({ params }) {
const postIndex = posts.findIndex(p => p.id === params.id);
if (postIndex === -1) {
throw error(404, 'Post not found');
}
posts.splice(postIndex, 1);
return new Response(null, { status: 204 });
}
Form Actions and Progressive Enhancement
<!-- src/routes/contact/+page.svelte -->
<script>
import { enhance } from '$app/forms';
import { page } from '$app/stores';
/** @type {import('./$types').PageData} */
export let data;
/** @type {import('./$types').ActionData} */
export let form;
let loading = false;
</script>
<svelte:head>
<title>Contact Us</title>
</svelte:head>
<div class="contact-page">
<h1>Contact Us</h1>
{#if form?.success}
<div class="success-message">
<p>Thank you for your message!</p>
<p>We'll get back to you within 24 hours.</p>
</div>
{:else}
<form
method="POST"
action="?/send"
use:enhance={({ formData, cancel }) => {
loading = true;
// Pre-submit processing
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
// Validation
if (!name || !email || !message) {
alert('Please fill in all fields');
cancel();
loading = false;
return;
}
return async ({ result, update }) => {
loading = false;
if (result.type === 'success') {
// Success handling
console.log('Contact form submitted successfully');
} else if (result.type === 'failure') {
// Error handling
console.error('Submission error:', result.data);
}
await update();
};
}}
>
<div class="form-group">
<label for="name">Name *</label>
<input
id="name"
name="name"
type="text"
required
value={form?.name ?? ''}
class:error={form?.errors?.name}
/>
{#if form?.errors?.name}
<span class="error">{form.errors.name}</span>
{/if}
</div>
<div class="form-group">
<label for="email">Email Address *</label>
<input
id="email"
name="email"
type="email"
required
value={form?.email ?? ''}
class:error={form?.errors?.email}
/>
{#if form?.errors?.email}
<span class="error">{form.errors.email}</span>
{/if}
</div>
<div class="form-group">
<label for="category">Category</label>
<select id="category" name="category">
<option value="general">General Inquiry</option>
<option value="support">Support</option>
<option value="business">Business</option>
<option value="bug">Bug Report</option>
</select>
</div>
<div class="form-group">
<label for="message">Message *</label>
<textarea
id="message"
name="message"
rows="5"
required
value={form?.message ?? ''}
class:error={form?.errors?.message}
></textarea>
{#if form?.errors?.message}
<span class="error">{form.errors.message}</span>
{/if}
</div>
<button type="submit" disabled={loading}>
{loading ? 'Sending...' : 'Send'}
</button>
{#if form?.errors?.general}
<div class="general-error">
{form.errors.general}
</div>
{/if}
</form>
{/if}
</div>
<style>
.contact-page {
max-width: 600px;
margin: 0 auto;
padding: 2rem;
}
.success-message {
background: #d4edda;
color: #155724;
padding: 1rem;
border-radius: 4px;
border: 1px solid #c3e6cb;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
input, select, textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
input.error, textarea.error {
border-color: #dc3545;
}
.error {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
}
button {
background: #ff3e00;
color: white;
padding: 0.75rem 2rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.general-error {
background: #f8d7da;
color: #721c24;
padding: 0.5rem;
border-radius: 4px;
margin-top: 1rem;
}
</style>
// src/routes/contact/+page.server.js
import { fail } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export function load() {
return {
title: 'Contact Us'
};
}
/** @type {import('./$types').Actions} */
export const actions = {
send: async ({ request }) => {
const data = await request.formData();
const name = data.get('name');
const email = data.get('email');
const category = data.get('category');
const message = data.get('message');
// Validation
const errors = {};
if (!name || name.length < 2) {
errors.name = 'Name must be at least 2 characters long';
}
if (!email || !email.includes('@')) {
errors.email = 'Please enter a valid email address';
}
if (!message || message.length < 10) {
errors.message = 'Message must be at least 10 characters long';
}
if (Object.keys(errors).length > 0) {
return fail(400, {
errors,
name,
email,
message
});
}
try {
// Would normally send email or save to database
console.log('Contact form received:', {
name,
email,
category,
message,
timestamp: new Date().toISOString()
});
// Simulate email sending
await simulateEmailSend({ name, email, category, message });
return {
success: true
};
} catch (error) {
console.error('Contact form submission error:', error);
return fail(500, {
errors: {
general: 'A server error occurred. Please try again later.'
},
name,
email,
message
});
}
}
};
async function simulateEmailSend(data) {
// Actual email sending logic
// Example: SendGrid, NodeMailer, etc.
return new Promise((resolve) => {
setTimeout(resolve, 1000); // Simulate 1-second delay
});
}
Deployment and Configuration
// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Svelte options
preprocess: vitePreprocess(),
kit: {
// Adapter configuration (auto-detect)
adapter: adapter(),
// Alias configuration
alias: {
$components: 'src/components',
$utils: 'src/utils',
$stores: 'src/stores'
},
// CSP (Content Security Policy) configuration
csp: {
mode: 'auto',
directives: {
'script-src': ['self']
}
},
// Preload configuration
preload: {
// Preload conditions
default: 'tap'
},
// Service worker configuration
serviceWorker: {
register: false
},
// Environment variables configuration
env: {
dir: process.cwd(),
publicPrefix: 'PUBLIC_'
}
}
};
export default config;
// vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()],
server: {
port: 5173,
strictPort: false,
},
preview: {
port: 4173,
strictPort: false,
},
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});
{
"name": "my-sveltekit-app",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2",
"vitest": "^0.34.0"
},
"type": "module",
"dependencies": {
"@sveltejs/adapter-netlify": "^2.0.8",
"@sveltejs/adapter-vercel": "^3.0.3"
}
}