Load data
A loader runs on the server for the first request and on navigation after that, and the page reads its result with a hook.
A route exports loader to fetch what the page needs. The page reads the result with useLoaderData.
Explore which files run on the server, in the browser, or on both sides.
import { type LoaderArgs, useLoaderData } from "@shadow-js/router";
import type { JSX } from "@shadow-js/web";
import type { User } from "../../server/schema";
import { getUser } from "../../server/users";
export function loader({ params }: LoaderArgs<"/users/:id", unknown>) {
return getUser({ id: params.id });
}
export default function UserPage(): JSX.Element {
const user = useLoaderData<User>();
return (
<article>
<h1>{user()?.name}</h1>
<p>{user()?.email}</p>
</article>
);
}
On the first request the loader runs on the server, and its result is written into the page, so the browser hydrates without asking again. When the reader follows a link to another user, the loader runs again for that route only, and only the parts of the page that read user() change.
Where the loader runs#
The loader is the same function in both cases. In the browser, the call to getUser is a request to the server: a server function is the thing that makes that safe, so the database code never reaches the browser.
When something is missing#
Throw notFound() for a missing record and the route's not-found page answers with a 404. Throw redirect("/login") to send the reader somewhere else. An unexpected error becomes a 500 with a generic body in production, and the detail goes to your error hook.
Next: server functions. Params, search validation and deferred data are in Routing.