JavaScript 7 min read

SvelteKit: Build Full-Stack Web Applications

Master SvelteKit for modern web development. Learn routing, server-side rendering, data loading, forms, and build performant full-stack applications.

MR

Moshiour Rahman

Advertisement

What is SvelteKit?

SvelteKit is a full-stack framework for building web applications with Svelte. It provides routing, server-side rendering, and optimized builds out of the box.

SvelteKit Features

FeatureDescription
File-based RoutingAutomatic routes from files
SSR/SSGServer and static rendering
API RoutesBuilt-in backend endpoints
FormsProgressive form handling
StreamingStreaming SSR support

Getting Started

Create Project

npm create svelte@latest my-app
cd my-app
npm install
npm run dev

Project Structure

my-app/
├── src/
│   ├── routes/
│   │   ├── +page.svelte
│   │   ├── +layout.svelte
│   │   └── api/
│   ├── lib/
│   │   └── components/
│   └── app.html
├── static/
├── svelte.config.js
└── vite.config.js

Routing

Basic Pages

<!-- src/routes/+page.svelte -->
<script>
  let count = 0;
</script>

<h1>Welcome to SvelteKit</h1>
<button on:click={() => count++}>
  Clicked {count} times
</button>

<style>
  h1 {
    color: #ff3e00;
  }
</style>

Dynamic Routes

<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  export let data;
</script>

<article>
  <h1>{data.post.title}</h1>
  <p>{data.post.content}</p>
</article>
// src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const post = await getPost(params.slug);

  if (!post) {
    throw error(404, 'Post not found');
  }

  return { post };
};

Nested Routes

routes/
├── dashboard/
│   ├── +layout.svelte     # Shared layout
│   ├── +page.svelte       # /dashboard
│   ├── settings/
│   │   └── +page.svelte   # /dashboard/settings
│   └── profile/
│       └── +page.svelte   # /dashboard/profile
<!-- src/routes/dashboard/+layout.svelte -->
<script>
  import Sidebar from '$lib/components/Sidebar.svelte';
</script>

<div class="dashboard">
  <Sidebar />
  <main>
    <slot />  <!-- Child routes render here -->
  </main>
</div>

Route Groups

routes/
├── (auth)/
│   ├── login/+page.svelte
│   └── register/+page.svelte
├── (app)/
│   ├── +layout.svelte     # Protected layout
│   ├── dashboard/+page.svelte
│   └── settings/+page.svelte

Data Loading

Server Load Functions

// src/routes/posts/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/db';

export const load: PageServerLoad = async ({ url, locals }) => {
  const page = Number(url.searchParams.get('page')) || 1;
  const limit = 10;

  const posts = await db.post.findMany({
    skip: (page - 1) * limit,
    take: limit,
    where: { published: true },
    orderBy: { createdAt: 'desc' }
  });

  const total = await db.post.count({ where: { published: true } });

  return {
    posts,
    pagination: {
      page,
      totalPages: Math.ceil(total / limit)
    }
  };
};

Universal Load Functions

// src/routes/posts/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ fetch }) => {
  // Runs on both server and client
  const response = await fetch('/api/posts');
  const posts = await response.json();

  return { posts };
};

Layout Data

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  return {
    user: locals.user
  };
};
<!-- src/routes/+layout.svelte -->
<script>
  export let data;
</script>

<nav>
  {#if data.user}
    <span>Welcome, {data.user.name}</span>
  {:else}
    <a href="/login">Login</a>
  {/if}
</nav>

<slot />

Form Actions

Server Actions

// src/routes/contact/+page.server.ts
import type { Actions } from './$types';
import { fail } from '@sveltejs/kit';

export const actions: Actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = data.get('email');
    const message = data.get('message');

    // Validation
    if (!email || !message) {
      return fail(400, {
        error: 'All fields are required',
        email,
        message
      });
    }

    // Process form
    await sendEmail({ email, message });

    return { success: true };
  }
};
<!-- src/routes/contact/+page.svelte -->
<script>
  import { enhance } from '$app/forms';
  export let form;
</script>

{#if form?.success}
  <p class="success">Message sent!</p>
{/if}

{#if form?.error}
  <p class="error">{form.error}</p>
{/if}

<form method="POST" use:enhance>
  <input
    name="email"
    type="email"
    value={form?.email ?? ''}
    required
  />

  <textarea
    name="message"
    required
  >{form?.message ?? ''}</textarea>

  <button type="submit">Send</button>
</form>

Named Actions

// src/routes/posts/[id]/+page.server.ts
export const actions: Actions = {
  publish: async ({ params }) => {
    await db.post.update({
      where: { id: params.id },
      data: { published: true }
    });
    return { success: true };
  },

  delete: async ({ params }) => {
    await db.post.delete({
      where: { id: params.id }
    });
    throw redirect(303, '/posts');
  }
};
<form method="POST" action="?/publish" use:enhance>
  <button>Publish</button>
</form>

<form method="POST" action="?/delete" use:enhance>
  <button>Delete</button>
</form>

API Routes

Endpoints

// src/routes/api/posts/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';

export const GET: RequestHandler = async ({ url }) => {
  const limit = Number(url.searchParams.get('limit')) || 10;

  const posts = await db.post.findMany({
    take: limit,
    where: { published: true }
  });

  return json(posts);
};

export const POST: RequestHandler = async ({ request, locals }) => {
  if (!locals.user) {
    throw error(401, 'Unauthorized');
  }

  const data = await request.json();

  const post = await db.post.create({
    data: {
      ...data,
      authorId: locals.user.id
    }
  });

  return json(post, { status: 201 });
};

Dynamic API Routes

// src/routes/api/posts/[id]/+server.ts
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async ({ params }) => {
  const post = await db.post.findUnique({
    where: { id: params.id }
  });

  if (!post) {
    throw error(404, 'Post not found');
  }

  return json(post);
};

export const PUT: RequestHandler = async ({ params, request }) => {
  const data = await request.json();

  const post = await db.post.update({
    where: { id: params.id },
    data
  });

  return json(post);
};

export const DELETE: RequestHandler = async ({ params }) => {
  await db.post.delete({
    where: { id: params.id }
  });

  return new Response(null, { status: 204 });
};

Authentication

Hooks

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  // Get session from cookie
  const sessionId = event.cookies.get('session');

  if (sessionId) {
    const session = await getSession(sessionId);
    if (session) {
      event.locals.user = session.user;
    }
  }

  const response = await resolve(event);
  return response;
};

Protected Routes

// src/routes/(protected)/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  if (!locals.user) {
    throw redirect(303, '/login');
  }

  return {
    user: locals.user
  };
};

Login/Logout

// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';

export const actions: Actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    const user = await authenticate(email, password);

    if (!user) {
      return fail(400, { error: 'Invalid credentials' });
    }

    const session = await createSession(user.id);

    cookies.set('session', session.id, {
      path: '/',
      httpOnly: true,
      sameSite: 'strict',
      secure: process.env.NODE_ENV === 'production',
      maxAge: 60 * 60 * 24 * 7  // 1 week
    });

    throw redirect(303, '/dashboard');
  }
};

State Management

Stores

// src/lib/stores/cart.ts
import { writable } from 'svelte/store';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

function createCart() {
  const { subscribe, set, update } = writable<CartItem[]>([]);

  return {
    subscribe,
    addItem: (item: CartItem) => {
      update(items => {
        const existing = items.find(i => i.id === item.id);
        if (existing) {
          existing.quantity += 1;
          return items;
        }
        return [...items, { ...item, quantity: 1 }];
      });
    },
    removeItem: (id: string) => {
      update(items => items.filter(i => i.id !== id));
    },
    clear: () => set([])
  };
}

export const cart = createCart();
<script>
  import { cart } from '$lib/stores/cart';

  $: total = $cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
</script>

<div class="cart">
  {#each $cart as item}
    <div>
      {item.name} x {item.quantity} = ${item.price * item.quantity}
      <button on:click={() => cart.removeItem(item.id)}>Remove</button>
    </div>
  {/each}
  <p>Total: ${total}</p>
</div>

Deployment

Build Configuration

// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
// import adapter from '@sveltejs/adapter-node';
// import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: {
    adapter: adapter(),
    alias: {
      '$components': './src/lib/components'
    }
  }
};

Environment Variables

// src/routes/+page.server.ts
import { env } from '$env/dynamic/private';
import { PUBLIC_API_URL } from '$env/static/public';

export const load = async () => {
  // Server-only
  const apiKey = env.API_KEY;

  // Public (available on client)
  const apiUrl = PUBLIC_API_URL;
};

Summary

FeaturePattern
Pages+page.svelte
Layouts+layout.svelte
Server Data+page.server.ts
Form Actionsactions export
API Routes+server.ts
Hookshooks.server.ts

SvelteKit provides an excellent developer experience for building performant full-stack applications.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.