Skip to content

Load Functions

Every serious app needs to fetch data before rendering a page. React’s ecosystem has handled this many ways over the years: react-router loaders, Next.js getServerSideProps, Next.js App Router server components. SvelteKit unifies the pattern into a single, predictable convention: a load function exported from a file next to your page.

Run this in a SvelteKit project (npx sv create).

react-router loader vs SvelteKit +page.js load

Section titled “react-router loader vs SvelteKit +page.js load”

react-router v6.4+ introduced loader functions — async functions that run before the route renders. SvelteKit’s +page.js load is the same idea, but the file is co-located with the page instead of embedded in a route config object.

React (react-router)
// react-router v6.4 — route config
import { createBrowserRouter } from 'react-router-dom';
import { useLoaderData } from 'react-router-dom';
async function postLoader({ params }) {
const res = await fetch('/api/posts/' + params.id);
return res.json();
}
function PostPage() {
const post = useLoaderData();
return <h1>{post.title}</h1>;
}
const router = createBrowserRouter([
{
path: '/posts/:id',
element: <PostPage />,
loader: postLoader,
},
]);
SvelteKit +page.js
// src/routes/posts/[id]/+page.js
export async function load({ params, fetch }) {
const res = await fetch('/api/posts/' + params.id);
const post = await res.json();
return { post };
}

The SvelteKit +page.js load runs on both the server (initial request) and the client (SvelteKit navigations). This means you get server-rendered HTML on first load and fast client-side transitions on subsequent navigations — for free.

Whatever load returns is available as data via \$props():

src/routes/posts/[id]/+page.svelte
<script>
let { data } = $props();
// data.post is the object returned from load()
</script>
<h1>{data.post.title}</h1>
<p>{data.post.body}</p>

TypeScript: import PageLoad (for +page.js) or PageServerLoad (for +page.server.js) from the auto-generated ./$types file:

// +page.js (universal)
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, fetch }) => {
const post = await fetch('/api/posts/' + params.id).then(r => r.json());
return { post };
};

Next.js getServerSideProps vs SvelteKit +page.server.js

Section titled “Next.js getServerSideProps vs SvelteKit +page.server.js”

getServerSideProps is Next.js Pages Router’s server-only data loader — it runs exclusively on the server, can access databases and secrets, and passes data as props to the page component. SvelteKit’s +page.server.js is the direct equivalent.

Next.js (getServerSideProps)
// Next.js Pages Router — pages/posts/[id].tsx
export async function getServerSideProps({ params }) {
const post = await db.post.findUnique({
where: { id: Number(params.id) },
});
return { props: { post } };
}
export default function PostPage({ post }) {
return <h1>{post.title}</h1>;
}
SvelteKit +page.server.js
// src/routes/posts/[id]/+page.server.js
import { db } from '$lib/db';
export async function load({ params, cookies }) {
const post = await db.post.findUnique({
where: { id: Number(params.id) },
});
return { post };
}

The +page.server.js version:

  • Runs only on the server — safe for DB queries, secrets, private env vars.
  • Receives cookies in addition to params, fetch, and url.
  • Returns a plain object; SvelteKit serialises it and passes it to the client as data.

Both +page.js and +page.server.js should signal a missing resource with the error() helper from @sveltejs/kit rather than throwing a plain object — SvelteKit renders a proper +error.svelte page for it. Since SvelteKit 2, error() throws internally, so you call it without a throw prefix:

src/routes/blog/[slug]/+page.server.js
import { db } from '$lib/db';
import { error } from '@sveltejs/kit';
export async function load({ params }) {
const post = await db.post.findUnique({ where: { slug: params.slug } });
if (!post) error(404, 'Not found');
return { post };
}

+page.js vs +page.server.js — when to use which

Section titled “+page.js vs +page.server.js — when to use which”
+page.js (universal)+page.server.js (server-only)
Runs on serverYes (initial request)Yes (always)
Runs on clientYes (navigations)No
Access cookiesNoYes
Access DB directlyNot recommendedYes
Access private env varsNoYes
fetch is availableYes (enhanced)Yes (enhanced)

A quick way to decide by scenario:

ScenarioFile to use
Calling a public REST API+page.js
Need fetching on both server (SSR) and client (navigation)+page.js
Querying a database directly+page.server.js
Reading cookies or a session+page.server.js
Using a secret API key+page.server.js

Rule of thumb: if the code must never appear in the browser bundle, put it in +page.server.js.

Both universal and server load functions receive the same core arguments:

// +page.js or +page.server.js
export async function load({ params, fetch, url, cookies }) {
// params — route parameters, e.g. params.slug
// fetch — SvelteKit-enhanced fetch (sets cookies, handles relative URLs)
// url — the current URL as a URL object
// cookies — (server only) read/write cookies
}
What is the difference between +page.js and +page.server.js in SvelteKit?
How does a SvelteKit page component receive data returned from its load() function?
Which load() argument is only available in +page.server.js, not +page.js?