Data is provided to the route via loader
and clientLoader
, and accessed in the data
prop of the Route Component.
clientLoader
is used to fetch data on the client. This is useful for projects that aren't server rendering, and for pages you'd prefer fetch their data in the browser.
// route("products/:pid", "./product.tsx");
import { defineRoute$ } from "react-router";
export default defineRoute$({
params: ["pid"],
async clientLoader({ params }) {
const res = await fetch(`/api/products/${params.pid}`);
const product = await res.json();
return { product };
},
component: function Product({ data }) {
return (
<div>
<h1>{data.product.name}</h1>
<p>{data.product.description}</p>
</div>
);
},
});
When server rendering, the loader
method is used to fetch data on the server for both initial page loads and client navigations through an automatic fetch
by React Router in the browser.
// route("products/:pid", "./product.tsx");
import { defineRoute$ } from "react-router";
import { fakeDb } from "../db";
export default defineRoute$({
params: ["pid"],
async loader({ params }) {
const product = await fakeDb.getProduct(params.pid);
return { product };
},
Component({ data }) {
return (
<div>
<h1>{data.product.name}</h1>
<p>{data.product.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.
RSC is supported by returning components from loaders and actions.
// route("products/:pid", "./product.tsx");
import { defineRoute$ } from "react-router";
import Product from "./product";
import Reviews from "./reviews";
export default defineRoute$({
params: ["pid"],
async loader({ params }) {
return {
product: <Product id={params.pid} />,
reviews: <Reviews productId={params.pid} />,
};
},
Component({ data }) {
return (
<div>
{data.product}
{data.reviews}
</div>
);
},
});
When pre-rendering, the loader
method is used to fetch data at build time.
// route("products/:pid", "./product.tsx");
import { defineRoute$ } from "react-router";
export default defineRoute$({
params: ["pid"],
async loader({ params }) {
let product = await getProductFromCSVFile(params.pid);
return { product };
},
Component({ data }) {
return (
<div>
<h1>{data.product.name}</h1>
<p>{data.product.description}</p>
</div>
);
},
});
The URLs to pre-render are specified in the Vite plugin.
import { plugin as app } from "@react-router/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
app({
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 clientside navigations.
// route("products/:pid", "./product.tsx");
import { defineRoute$ } from "react-router";
import { fakeDb } from "../db";
export default defineRoute$({
// SSR loads directly from the database
async loader({ params }) {
return fakeDb.getProduct(params.pid);
},
// client navigations fetch directly from the browser,
// skipping the react router server
async clientLoader({ params }) {
const res = await fetch(`/api/products/${params.pid}`);
return res.json();
},
Component({ data }) {
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
</div>
);
},
});
For more advanced use cases with clientLoader
like caching, refer to Advanced Data Fetching.