Data Loading

Co-locate a server loader with a page; its data is rendered on the server and hydrated on the client — no waterfalls, no client fetch on first paint.

A loader runs on the server before a page renders. What awaits it, renders the page with the resolved data in scope, and serializes that data into the HTML so the client hydrates without re-fetching. This is the data path for SSR and ISR pages.

The loader export

Export a function named loader from a page module. It receives { params, query, request } and returns data (sync or async).

// src/pages/blog/[slug].jsx
export const loader = async ({ params, query, request }) => {
  const post = await db.posts.find(params.slug);
  return { post };
};

export default function Post() {
  const { post } = useLoaderData();
  return <article><h1>{post.title}</h1><p>{post.body}</p></article>;
}
  • params — dynamic route segments (e.g. { slug: 'hello' } for /blog/[slug]).
  • query — parsed query string as an object.
  • request — the standard Request (read cookies/headers for auth).

Runs before render

The loader resolves outside the synchronous render, so the value handed to the component is plain data — never a promise. That keeps render fast and concurrency-safe.

useLoaderData

useLoaderData() returns the current page's loader data. It is isomorphic: on the server it reads the render-scoped context; on the client it reads the hydration payload (<script id="__what_data">). It is not a hook-slot consumer, so you can call it anywhere in a component.

import { useLoaderData } from 'what-framework';

function Post() {
  const { post } = useLoaderData();
  // ...
}

getStaticPaths

For dynamic routes that should be pre-rendered, export getStaticPaths. It returns the set of paths to build ahead of time plus a fallback policy for everything else.

export async function getStaticPaths() {
  const posts = await db.posts.all();
  return {
    paths: posts.map((p) => ({ params: { slug: p.slug } })),
    fallback: 'blocking', // 'blocking' | true | false
  };
}
  • 'blocking' — an unbuilt path renders on first request, then is cached (default-feeling, great for ISR).
  • true — serve a skeleton immediately, then swap in the rendered page.
  • false — anything not in paths is a 404.

Because getStaticPaths is a function (it can't be JSON), it is a named export, never part of the page config object.

Loaders vs client fetching

Use a loader for data the page needs to render on the server (SEO content, the primary record). Use the client data-fetching hooks (useSWR, useQuery) for data that loads after hydration (user-specific widgets, polling dashboards). They compose: a loader seeds the first paint, hooks keep it live.