Data is provided to the route component from loader
and clientLoader
.
clientLoader
is used to fetch data on the client. This is useful for pages or full projects that you'd prefer to fetch data from the browser only.
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types.product";
export async function clientLoader({
params,
}: Route.ClientLoaderArgs) {
const res = await fetch(`/api/products/${params.pid}`);
const product = await res.json();
return product;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
When server rendering, loader
is used for both initial page loads and client navigations. Client navigations call the loader through an automatic fetch
by React Router from the browser to your server.
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types.product";
import { fakeDb } from "../db";
export async function loader({ params }: Route.LoaderArgs) {
const product = await fakeDb.getProduct(params.pid);
return product;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
Note that the loader
function is removed from client bundles so you can use server only APIs without worrying about them being included in the browser.
If you need to return a custom HTTP status code or custom headers from your loader
, you can do so using the data
utility:
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types.product";
import { data } from "react-router";
import { fakeDb } from "../db";
export function headers({ loaderHeaders }: HeadersArgs) {
return loaderHeaders;
}
export async function loader({ params }: Route.LoaderArgs) {
const product = await fakeDb.getProduct(params.pid);
if (!product) {
throw data(null, { status: 404 });
}
return data(product, {
headers: {
"Cache-Control": "public; max-age=300",
},
});
}
When pre-rendering, loaders are used to fetch data during the production build.
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types.product";
export async function loader({ params }: Route.LoaderArgs) {
let product = await getProductFromCSVFile(params.pid);
return product;
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
The URLs to pre-render are specified in the Vite plugin.
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
reactRouter({
async prerender() {
let products = await readProductsFromCSVFile();
return products.map(
(product) => `/products/${product.id}`
);
},
}),
],
});
Note that when server rendering, any URLs that aren't pre-rendered will be server rendered as usual.
loader
and clientLoader
can be used together. The loader
will be used on the server for initial SSR (or pre-rendering) and the clientLoader
will be used on subsequent client-side navigations.
// route("products/:pid", "./product.tsx");
import type { Route } from "./+types.product";
import { fakeDb } from "../db";
export async function loader({ params }: Route.LoaderArgs) {
return fakeDb.getProduct(params.pid);
}
export async function clientLoader({
params,
}: Route.ClientLoader) {
const res = await fetch(`/api/products/${params.pid}`);
return res.json();
}
export default function Product({
loaderData,
}: Route.ComponentProps) {
const { name, description } = loaderData;
return (
<div>
<h1>{name}</h1>
<p>{description}</p>
</div>
);
}
For more advanced use cases with clientLoader
like caching, refer to Advanced Data Fetching.
In the future, rendered async components in loaders are available on loaderData
like any other value:
// route("products/:pid", "./product-page.tsx");
import type { Route } from "./+types.product";
import Product from "./product";
import Reviews from "./reviews";
export async function loader({ params }: Route.LoaderArgs) {
return {
product: <Product id={params.pid} />,
reviews: <Reviews productId={params.pid} />,
};
}
export default function ProductPage({
loaderData,
}: Route.ComponentProps) {
return (
<div>
{loaderData.product}
<Suspense fallback={<div>loading...</div>}>
{loaderData.reviews}
</Suspense>
</div>
);
}
export async function Product({ id }: { id: string }) {
const product = await fakeDb.getProduct(id);
return (
<div>
<h1>{product.title}</h1>
<p>{product.description}</p>
</div>
);
}