SvelteKit

Official full-stack framework for Svelte. Achieves high performance and small bundle sizes through compile-time optimization. Rapidly growing attention in 2025.

JavaScriptSvelteFrameworkSSRSSGRoutingMeta-frameworkFull-stack

GitHub Overview

sveltejs/kit

web development, streamlined

Stars19,601
Watchers156
Forks2,096
Created:October 15, 2020
Language:JavaScript
License:MIT License

Topics

hacktoberfestsvelte

Star History

sveltejs/kit Star History
Data as of: 8/13/2025, 01:43 AM

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

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>&copy; 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"
  }
}