SvelteKit Data Loading & Final Patterns

Chapter 12
Data Loading & Final Patterns
load functions, form actions, SSR, and deploying a SvelteKit app

The final chapter covers how SvelteKit gets data into pages and data back out via forms — the pieces that make it a full-stack framework rather than just a router. This is where SvelteKit's server-rendering story (its big advantage over the plain Vite SPA from Chapter 1) really shows, and where it lines up directly against Next.js and Nuxt.

The load Function — Data Before the Page Renders

src/routes/blog/ ├── +page.svelte // the page (UI) └── +page.js // the load function (data)
// src/routes/blog/+page.js export async function load({ fetch }) { const res = await fetch('/api/posts'); const posts = await res.json(); return { posts }; // becomes the page's `data` prop }
<!-- src/routes/blog/+page.svelte --> <script> let { data } = $props(); // data.posts from load() </script> <ul> {#each data.posts as post (post.id)} <li>{post.title}</li> {/each} </ul>

A +page.js beside a page exports a load function that runs before the page renders; whatever it returns arrives as the page's data prop. This is a real shift from the Chapter 9 pattern of fetching in onMountload runs ahead of render (on the server for the first load), so the page arrives with its data already present, no loading flash, and it's SEO-friendly. It's the direct equivalent of Next.js server components / getServerSideProps and Nuxt's useAsyncData.

+page.js vs +page.server.js
A load in +page.js can run on both server and client (a "universal" load — use it for public API calls). A load in +page.server.js runs only on the server — the place for database queries, secret API keys, and anything that must never reach the browser. Choosing the right file is how you keep secrets server-side: code in .server.js is never bundled to the client.

Dynamic Routes Get Their Param in load

// src/routes/product/[id]/+page.js export async function load({ params, fetch }) { const res = await fetch(`/api/products/${params.id}`); return { product: await res.json() }; }

The load function receives params, so a dynamic [id] route (Chapter 11) fetches exactly the right record server-side — params.id here is the same value page.params.id gave you in the component, but available before render so the data ships with the page.

Form Actions — Data Back to the Server

// src/routes/contact/+page.server.js export const actions = { default: async ({ request }) => { const data = await request.formData(); const email = data.get('email'); // ...save it server-side... return { success: true }; }, };
<!-- +page.svelte: a real form that POSTs to the action --> <form method="POST"> <input name="email" type="email" /> <button>Submit</button> </form>

Form actions handle submissions on the server. A +page.server.js exports an actions object, and a standard HTML <form method="POST"> posts to it — no onsubmit handler, no manual fetch, and crucially it works without JavaScript (progressive enhancement). This is SvelteKit's signature data-out pattern, paralleling Next.js Server Actions and Remix's form actions — the framework leaning into web-standard forms rather than client-side-only handlers.

use:enhance — progressive enhancement, upgraded
Adding the use:enhance action to the form (<form method="POST" use:enhance>, imported from $app/forms) keeps the no-JS behavior but, when JS is available, submits via fetch with no full-page reload and updates the page in place. You get the resilient baseline and the SPA-smooth experience from the same markup — the best of both, which is hard to achieve in a purely client-side framework.

SSR, CSR, and Prerendering

SvelteKit renders on the server by default (SSR) — the first page arrives as real HTML (fast first paint, SEO-friendly), then "hydrates" into an interactive client app. You can tune this per route with page options: export const prerender = true turns a route into a static file at build time (ideal for content that rarely changes — a marketing or docs page), and export const ssr = false makes a route client-only (for something that can't run on the server). This per-route control over SSR / static / client-only is exactly the spectrum Next.js and Nuxt offer.

Deployment — Adapters

// svelte.config.js import adapter from '@sveltejs/adapter-auto'; export default { kit: { adapter: adapter() }, };

SvelteKit deploys via adapters — small plugins that package the build for a target platform. adapter-auto detects common hosts (Vercel, Netlify, Cloudflare); there's adapter-node for a plain Node server and adapter-static for a fully static site. You write the same app and swap the adapter for the destination — the same "deploy anywhere" promise as Nuxt's presets and Next.js's hosting flexibility.

Don't fetch in onMount when load will do — and watch the server/client line
Two habits to carry forward. First: in a SvelteKit app, prefer a load function over an onMount fetch for a page's primary data — you get SSR, no loading flash, and better SEO. Reserve onMount fetching for genuinely client-only, after-render needs. Second: be deliberate about the +page.js vs +page.server.js split — secrets, database access, and private keys belong in .server.js so they never ship to the browser.
SvelteKitNext.jsNuxt
load in +page.js / +page.server.jsserver components / getServerSidePropsuseAsyncData / useFetch
form actions + use:enhanceServer Actionsserver routes + useFetch
SSR default + prerender / ssr optsSSG / SSR / RSC per routeSSR / SSG / hybrid
adapters (auto / node / static)built-in + hosting targetsnitro presets

Coding Challenges

Challenge 1

Build a /posts route with a +page.js load function fetching from any free public API and returning { posts }, and a +page.svelte that reads data via $props and lists them — with no onMount and no loading state needed.

📄 View solution
Challenge 2

Build a /posts/[id] route whose +page.js load uses params.id to fetch a single post and return it, with the +page.svelte displaying the post's title and body from data.

📄 View solution
Challenge 3

Build a /contact route with a +page.server.js exposing a default form action that reads email/message from formData and returns { success: true }, plus a +page.svelte with a method="POST" form using use:enhance, showing a thank-you message from the action's result.

📄 View solution

Chapter 12 Quick Reference

  • load in +page.js — runs before render; its return becomes the page's data prop (= getServerSideProps)
  • +page.js = universal (server + client); +page.server.js = server-only (secrets, DB)
  • load({ params }) — dynamic-route data fetched server-side before render
  • Form actions (actions in +page.server.js + <form method="POST">) — work without JS
  • use:enhance — keeps no-JS fallback, adds fetch-based no-reload submit when JS is present
  • SSR by default; per-route prerender / ssr options; deploy via adapters
  • Prefer load over onMount for a page's primary data (SSR, no flash, SEO)

★ Svelte Course Complete — 12 / 12 chapters

From the compiler model and runes through components, snippets, shared reactive modules, and the full SvelteKit stack. You now have the same conceptual map across React, Angular, Vue, and Svelte — and can read the differences as variations on shared ideas rather than four separate worlds.