| name | sveltekit |
| description | [Applies to: **/*.{js,jsx,ts,tsx,svelte}] Definitive guidelines for building scalable, performant, and maintainable SvelteKit applications using modern best practices, TypeScript, and Svelte 5 runes. |
| source | cursor_mdc |
SvelteKit Best Practices
This guide outlines the definitive best practices for developing SvelteKit applications. Adhere to these principles for consistent, high-quality, and performant code.
1. Code Organization and Structure
Maintain a clean, scalable project structure.
src/routes/: All page and API endpoints. Co-locate +page.svelte, +page.ts, +layout.svelte, +error.svelte, +server.ts files.
src/lib/: Reusable utilities, components, and client-side logic. Imported via $lib.
src/lib/server/: Server-only modules. Imported via $lib/server. SvelteKit prevents client-side import.
src/params/: Custom route matchers.
static/: Static assets (e.g., favicon.png, robots.txt).
tests/: End-to-end (E2E) tests (e.g., Playwright). Unit tests can be co-located with src/.
Rule: Use + prefixed filenames for SvelteKit's routing and special files.
❌ BAD:
src/routes/users/index.svelte
src/routes/api/users.ts
✅ GOOD:
src/routes/users/+page.svelte
src/routes/api/users/+server.ts
2. Component Architecture
Design components for reusability and clarity.
Rule: Use lang="ts" for TypeScript in Svelte components and leverage $props() for type-safe props.
<!-- src/lib/components/Button.svelte -->
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
import type { Snippet } from 'svelte';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
children: Snippet;
}
let { variant = 'primary', children, ...rest }: Props = $props();
</script>
<button class:primary={variant === 'primary'} class:secondary={variant === 'secondary'} {...rest}>
{@render children()}
</button>
<style>
button { /* ... base styles ... */ }
.primary { /* ... primary styles ... */ }
.secondary { /* ... secondary styles ... */ }
</style>
3. State Management
Prioritize Svelte 5 runes for local reactivity and classes for complex, encapsulated state.
Rule: Use $state() for local, reactive component state. For complex logic or shared state, encapsulate it within a class.
❌ BAD (Old Svelte 4 store for local state):
<script lang="ts">
import { writable } from 'svelte/store';
const count = writable(0);
</script>
<button on:click={() => count.update(n => n + 1)}>Count: {$count}</button>
✅ GOOD (Svelte 5 runes for local state):
<script lang="ts">
let count = $state(0);
</script>
<button on:click={() => count++}>Count: {count}</button>
✅ GOOD (Class for complex/shared state):
class CounterStore {
count = $state(0);
increment() {
this.count++;
}
reset() {
this.count = 0;
}
}
export const counter = new CounterStore();
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { counter } from '$lib/stores/counter';
</script>
<button on:click={() => counter.increment()}>Count: {counter.count}</button>
4. Routing and Data Fetching
Leverage SvelteKit's file-system routing and load functions.
Rule: Use +page.ts for universal load functions (SSR + CSR) and +page.server.ts for server-only load functions or form actions. Always type load functions with $types.
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, fetch }) => {
const res = await fetch(`/api/blog/${params.slug}`);
if (!res.ok) {
error(res.status, 'Could not fetch blog post.');
}
const post = await res.json();
return { post };
};
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>{data.post.title}</h1>
<p>{data.post.content}</p>
Rule: For API endpoints, use +server.ts files.
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ params }) => {
if (params.slug === 'hello-world') {
return json({ title: 'Hello World', content: 'Welcome to our blog!' });
}
error(404, 'Not Found');
};
5. Performance Considerations
Optimize for speed and efficiency.
Rule: Prerender static routes by exporting export const prerender = true in +page.ts or +layout.ts. Use @sveltejs/enhanced-img for image optimization.
export const prerender = true;
Rule: Deploy to platforms like Vercel for zero-config serverless functions and automatic optimizations.
6. Accessibility
Build inclusive user interfaces.
Rule: Always use semantic HTML, ensure keyboard navigability, and test with screen readers.
❌ BAD:
<div on:click={doSomething}>Click me</div>
✅ GOOD:
<button on:click={doSomething}>Click me</button>
7. Testing Approaches
Implement a robust testing strategy.
Rule: Use Playwright for end-to-end tests in tests/ and Vitest for unit tests, co-located with the code they test. Integrate svelte-check, ESLint, and Prettier into your CI pipeline.
import { expect, test } from 'vitest';
import { add } from './math';
test('add function sums numbers correctly', () => {
expect(add(1, 2)).toBe(3);
});
8. Common Pitfalls and Gotchas
Avoid common mistakes that lead to bugs or performance issues.
Rule: Do not import $lib/server modules into client-side code. SvelteKit will prevent this at build time, but catching it early saves time.
❌ BAD (in +page.ts or +page.svelte):
import { serverOnlyFunction } from '$lib/server/utils';
✅ GOOD (in +page.server.ts or +server.ts):
import { serverOnlyFunction } from '$lib/server/utils';
Rule: Configure tsconfig.json correctly, extending SvelteKit's generated config. Specifically, ensure verbatimModuleSyntax: true and isolatedModules: true.
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"target": "ES2022",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"module": "ESNext",
"resolveJsonModule": true,
"allowJs":