Learn SvelteKit - Data Loading & Forms
Episode 5 of 24

Learn SvelteKit - Data Loading & Forms

This episode covers the data flow in SvelteKit: load functions on server and client, fetching data with fetch inside load, form handling with server actions and progressive enhancement, plus error and redirect handling. You will understand how data flows from the server to components.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Static pages are only the beginning. Real applications need data: article lists, user details, or search results. Episode 5 covers the two core SvelteKit mechanisms that drive data: load functions to fetch data before rendering, and server actions to process input from forms.

Both are designed for a single experience: the same mental model on both server and client. The same load function can run during SSR to deliver fast HTML, then run again on the client for instant subsequent navigations.

By the end of this episode you can build pages that fetch dynamic data and safely process user input, without writing a manual API endpoint for every case.

Load Functions on Server and Client

Server Load with +page.server.js

The +page.server.js file contains a load function that only runs on the server. This is the right place to access a database, read secrets, or perform a redirect before the page is rendered.

JSServer load
export const load = async (event) => {
    const { params, locals } = event;
    const artikel = await cariArtikel(params.slug);
 
    if (!artikel) {
        throw redirect(303, "/artikel");
    }
 
    return { artikel };
};

The returned data is serialized and sent to the client. Values that can't be serialized, like class instances or functions, never reach the browser.

Universal Load with +page.js

The +page.js file (without .server) runs on the server during SSR and on the client during navigation. It's suited for public data that may be fetched from the browser.

JSUniversal load
export const load = async ({ fetch }) => {
    const res = await fetch("/api/artikel");
    const daftar = await res.json();
 
    return { daftar };
};

During SSR, the built-in fetch in a load function runs on the server and follows the original request's cookies. On the client, the same fetch runs normally. This makes the code identical on both sides.

Fetching Data Inside Load

Using event.fetch

The fetch function available as a load argument is smarter than the global fetch: it forwards cookies, runs on the server during SSR, and detects internal requests to your own routes so they don't add network load.

Use event.fetch when you want to call your application's own +server.js endpoints, or fetch data from an external API with the same headers. The result can be cached and streamed together with the page.

When Server, When Universal

The rule of thumb:

  • Database, secrets, cookies, and redirects → +page.server.js.
  • Public data, simple aggregations, or results that need to reach the client for interactivity → +page.js.
  • Need both? Put the shared logic in a src/lib module and call it from both sides.

To prevent sensitive data from leaking, never load secrets in a universal load — +page.js code is sent to the browser and can be read by anyone.

Form Handling with Server Actions

Actions in +page.server.js

SvelteKit offers server actions: functions in +page.server.js that handle form submissions. The form is sent as a POST, and the result updates client state without a full reload.

JSServer action
export const actions = {
    login: async ({ request }) => {
        const form = await request.formData();
        const email = String(form.get("email") ?? "");
        const password = String(form.get("password") ?? "");
 
        if (!email || !password) {
            return { error: "Email dan password wajib diisi" };
        }
 
        return { success: true };
    }
};

Progressive Enhancement with use:enhance

In the component, point the form at an action using the action attribute containing a question mark followed by the action name, then add the use:enhance directive for progressive enhancement.

Form with use:enhance
<script>
    import { enhance } from "$app/forms";
 
    let { form } = $props();
</script>
 
<form method="POST" action="?/login" use:enhance>
    <input name="email" type="email" placeholder="Email" />
    <input name="password" type="password" placeholder="Password" />
    <button type="submit">Masuk</button>
</form>
 
{#if form?.error}
    <p class="error">{form.error}</p>
{/if}

With use:enhance, the submission is sent via fetch: the form doesn't lose state, animations can be added, and the action result is immediately available. Without JavaScript, the form still works because it falls back to the native POST mechanism — this is the essence of progressive enhancement.

Error Handling and Redirects

Redirect with throw

To move the user inside a load or action, throw a redirect:

JSRedirect
import { redirect } from "@sveltejs/kit";
 
export const actions = {
    logout: async (event) => {
        event.cookies.delete("session", { path: "/" });
        throw redirect(303, "/login");
    }
};

Use status code 303 after POST (the PRG pattern), 301 for permanent redirects, and 307 when the method must be preserved. throw redirect(303, "/login") always stops function execution.

Errors with throw error

For genuinely failing situations, use the error helper from @sveltejs/kit:

JSThrow a structured error
import { error } from "@sveltejs/kit";
 
export const load = async ({ params }) => {
    const artikel = await cariArtikel(params.slug);
 
    if (!artikel) {
        throw error(404, "Artikel tidak ditemukan");
    }
 
    return { artikel };
};

Thrown errors are rendered by +error.svelte at the nearest layout level. You can show a friendly message, status code, and a back button — much better than the browser's default error page.

Closing

Key takeaways:

  • Load functions supply data to pages; +page.server.js is server-only, +page.js also runs on the client.
  • event.fetch in load forwards cookies, runs on the server during SSR, and is efficient for internal requests.
  • Server actions in +page.server.js handle form submissions without a manual endpoint.
  • use:enhance provides progressive enhancement; without JavaScript the form still works.
  • throw redirect stops execution and moves the user; throw error shows a structured error page.
  • Data in +page.js is sent to the browser, so never put secrets there.

In the next episode we shift to the presentation side: reactive UI & components. You'll learn Svelte component basics, reactive statements and bindings, slots and context modules, and scoped styling with CSS.

Learn SvelteKit - Data Loading & Forms | Learn SvelteKit