SvelteKit 1.0: Full-Stack Web Development with Svelte

SvelteKit 1.0 officially landed on December 14, 2022, and I have been waiting for this one. Svelte has always been the framework that feels like writing plain HTML, CSS, and JavaScript with superpowers. SvelteKit takes that foundation and turns it into a proper full-stack framework with file-based routing, server-side rendering, and deployment adapters that let you ship to basically any hosting target.
I have been building side projects with it over the last few months while it was still in beta, and I want to walk through what makes it compelling, how the core patterns work, and where it stacks up against Next.js.
File-Based Routing
SvelteKit uses a file-based routing system, but with a twist that I really like. Every route is a directory inside src/routes, and each directory can contain multiple files that define different aspects of the route. The naming convention uses a plus sign prefix to distinguish special files.
The main file is +page.svelte, which is your page component. If you need server-side data fetching, you create a +page.server.ts file alongside it. If you need client-side data fetching or universal logic, you use +page.ts instead. This separation is cleaner than the single-file approach in older frameworks because your data logic and your presentation logic live in separate files but are co-located in the same directory.
src/routes/
+page.svelte // Home page
+layout.svelte // Root layout
about/
+page.svelte // /about
blog/
+page.svelte // /blog (list)
+page.server.ts // Server-side data for blog list
[slug]/
+page.svelte // /blog/:slug (detail)
+page.server.ts // Server-side data for single post
Dynamic route parameters use square brackets, just like Next.js. The [slug] directory gives you access to params.slug in your load function. Catch-all routes use [...rest]. Route groups use parentheses like (auth) to organize routes without affecting the URL structure.
Load Functions
Load functions are how you fetch data in SvelteKit. They run before the page renders and pass data as props to your +page.svelte component. The server variant runs exclusively on the server, meaning you can safely use database connections, secrets, and any server-only dependencies.
// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/database';
export const load: PageServerLoad = async ({ params }) => {
const post = await db.post.findUnique({
where: { slug: params.slug }
});
if (!post) {
throw error(404, 'Post not found');
}
return { post };
};
The $types import is auto-generated by SvelteKit. It gives you full type safety between your load function and your page component. When you return { post } from the load function, your +page.svelte automatically gets typed props that include post. No manual type wiring needed.
On the page side, consuming the data is straightforward:
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
export let data;
</script>
<article>
<h1>{data.post.title}</h1>
<div>{@html data.post.content}</div>
</article>
Form Actions
Form actions are where SvelteKit really differentiates itself. Instead of wiring up API endpoints and client-side fetch calls for mutations, you define actions directly in your +page.server.ts file. These actions handle standard HTML form submissions, which means they work even without JavaScript enabled.
// src/routes/blog/new/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { db } from '$lib/server/database';
export const actions: Actions = {
default: async ({ request }) => {
const formData = await request.formData();
const title = formData.get('title') as string;
const content = formData.get('content') as string;
if (!title || title.length < 3) {
return fail(400, { title, content, error: 'Title must be at least 3 characters' });
}
const slug = title.toLowerCase().replace(/\s+/g, '-');
await db.post.create({ data: { title, slug, content } });
throw redirect(303, '/blog/' + slug);
}
};
The fail function lets you return validation errors back to the form while preserving the submitted values. The form component on the client side can access these errors through the form prop and use SvelteKit's enhance directive for progressive enhancement. When JavaScript is available, the form submission happens via fetch and the page updates without a full reload. When JavaScript is not available, it falls back to a standard form POST. This is how the web should work.
Layout Inheritance
Layouts in SvelteKit cascade naturally through the route tree. A +layout.svelte file wraps all routes in its directory and all child directories. The root layout at src/routes/+layout.svelte wraps your entire application. Nested layouts compose on top of each other.
Layouts can also have their own load functions via +layout.server.ts. This is useful for things like loading the current user session. The data from a layout's load function is available to all child pages, so you load the user once at the root layout and every page can access it.
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ cookies }) => {
const sessionId = cookies.get('session');
const user = sessionId ? await getUser(sessionId) : null;
return { user };
};
Adapters
One of SvelteKit's best features is its adapter system. Adapters transform your SvelteKit app for different deployment targets. The official adapters cover most use cases:
adapter-autodetects your deployment platform and configures itself (works with Vercel, Netlify, Cloudflare Pages)adapter-nodebuilds a standalone Node.js serveradapter-staticpre-renders your entire site as static HTMLadapter-verceloptimizes for Vercel's serverless and edge functionsadapter-cloudflaretargets Cloudflare Workers
You configure the adapter in svelte.config.js:
import adapter from '@sveltejs/adapter-node';
export default {
kit: {
adapter: adapter({ out: 'build' })
}
};
The adapter abstraction is elegant. You write your app once and swap the deployment target by changing one line of config. No rewriting server code, no conditional platform checks.
Error Handling
SvelteKit has built-in error handling through +error.svelte files. When a load function throws an error (using the error helper), SvelteKit renders the nearest +error.svelte component in the route tree. You can have different error pages for different sections of your app.
There is also a handleError hook in hooks.server.ts for catching unexpected errors, logging them, and transforming them before they reach the client. This is the place to integrate with error tracking services like Sentry.
How It Compares to Next.js
I have been working with Next.js for years, so I naturally compare every full-stack framework against it. Here is where SvelteKit wins and where Next.js still has the edge.
SvelteKit's form actions are better than anything Next.js had at this point in 2022. The progressive enhancement story, where forms work without JavaScript and get enhanced when it is available, is more mature and more principled. Next.js was still figuring out Server Actions at this time.
Svelte's reactivity model, where you just assign to a variable and the UI updates, produces less code than React's useState/useEffect patterns. Components feel lighter. There is no virtual DOM diffing overhead. The compiler does the work at build time.
Where Next.js wins is ecosystem. The React ecosystem is massive. Every UI library, every component kit, every integration is React-first. When you need a rich text editor, a date picker, or a charting library, you have dozens of battle-tested options in React. In Svelte, your choices are more limited.
Next.js also has a larger community, more tutorials, more StackOverflow answers, and more production deployments to learn from. If you are building a product that needs to scale a team, hiring React developers is much easier than hiring Svelte developers.
When I Would Reach for SvelteKit
SvelteKit is my first choice for content-driven sites, marketing pages, internal tools, and small to medium applications where I am the primary developer or working with a small team. The developer experience is genuinely better. You write less code, the mental model is simpler, and the build output is smaller.
For large applications with complex state management, heavy integrations, and big teams, I still lean toward Next.js because of the React ecosystem. But SvelteKit 1.0 landing as a stable release changes the conversation. It is no longer an experiment. It is a production-ready framework that deserves serious consideration for your next project.