SvelteKit Best Practices
Habits that keep a SvelteKit app fast, secure, and maintainable as it grows past a first prototype.
阅读需 3 分钟
Most of what makes a SvelteKit app hold up under real traffic and a real team isn't exotic — it's applying the conventions from earlier lessons consistently, instead of reaching for a shortcut the one time it seems easier.
Keep secrets out of universal code, always
// Wrong — this file can run in the browser
// src/routes/account/+page.js
import { db } from '$lib/server/database'; // build error, and rightly so// Right — server-only file, server-only import
// src/routes/account/+page.server.js
import { db } from '$lib/server/database';If you ever find yourself fighting SvelteKit's $lib/server or $env/*/private boundaries, that's a signal to move the code into a +page.server.js, +server.js, or hooks.server.js — not a reason to look for a workaround. These boundaries exist specifically to make leaking a secret require real effort.
Fetch with the provided fetch, not the global one
Inside any load function, always use the fetch passed as a parameter:
export async function load({ fetch }) {
const res = await fetch('/api/data'); // correct
return { data: await res.json() };
}The provided fetch handles relative URLs correctly during SSR and deduplicates a request that was already made on the server so the browser doesn't repeat it during hydration. The global fetch does neither.
Don't over-fetch in layouts
Data returned from a +layout.server.js load reruns and stays loaded for every nested page — great for genuinely shared data like the current user, wasteful for something only one child page actually needs. Push data-fetching as deep into the route tree as the data's actual scope, rather than defaulting to the root layout because it's convenient.
Build forms server-first
As covered earlier, a form action that works as a plain POST — then gets use:enhance layered on top — degrades gracefully and is easier to reason about than one built JavaScript-first. Treat this as the default for every form, not a special case for accessibility-conscious projects.
Use error() and redirect() instead of manual responses
// Prefer this
import { error } from '@sveltejs/kit';
if (!post) error(404, 'Not found');
// Over manually constructing a Response and hoping the framework handles it rightThese helpers integrate with SvelteKit's own error and redirect boundaries (+error.svelte, proper HTTP status codes) automatically — reinventing that plumbing by hand is easy to get subtly wrong (wrong status code, missed edge case in a catch block).
Lean on generated types
./$types catches a renamed field or a shape mismatch between a load function and the component consuming it at build time, in your editor, instead of at runtime for a real user. In a TypeScript project, ignoring the generated types and reaching for any defeats the one thing that makes the load-to-data contract safe by default.
Pick rendering options deliberately, not by copy-paste
ssr = false, csr = false, and prerender = true each trade something specific away. Set them because a route's actual requirements call for it — a truly static legal page, a genuinely personalized dashboard — not because a snippet from a forum post included one and it happened to work.
The common thread
Every practice here is really the same idea: SvelteKit draws clear lines — server versus universal code, page data versus form data, static versus dynamic rendering — specifically so you don't have to hold those distinctions in your head by discipline alone. Working with those lines, instead of around them, is most of what "writing idiomatic SvelteKit" means in practice.