Documentation Index Fetch the complete documentation index at: https://mintlify.com/remix-run/react-router/llms.txt
Use this file to discover all available pages before exploring further.
loader
A server-side function that loads data for a route before it renders.
Signature
export function loader ( args : LoaderFunctionArgs ) : Promise < Response | Data > | Response | Data
args
LoaderFunctionArgs
required
Arguments passed to the loader function A Fetch Request instance that you can use to read headers, cookies, and URLSearchParams
Dynamic route params for the current route // For route: /teams/:teamId
params . teamId // string
The context passed to your server adapter’s getLoadContext() function. Used to bridge the gap between the adapter’s request/response API and your React Router app.
The un-interpolated route pattern (e.g., /blog/:slug). Useful for logging/tracing.
Can return:
A Response object (with json(), redirect(), etc.)
Plain data (automatically serialized)
A Promise resolving to either
Basic Example
// app/routes/team.tsx
import { useLoaderData } from "react-router" ;
export async function loader ({ params } : Route . LoaderArgs ) {
const team = await fetchTeam ( params . teamId );
return { team };
}
export default function Team () {
const { team } = useLoaderData < typeof loader >();
return < h1 > { team . name } </ h1 > ;
}
Returning Responses
import { redirect } from "react-router" ;
export async function loader ({ request } : Route . LoaderArgs ) {
const user = await getUser ( request );
if ( ! user ) {
throw redirect ( "/login" );
}
return Response . json ({ user }, {
headers: {
"Cache-Control" : "max-age=300"
}
});
}
Reading Request Data
export async function loader ({ request , params } : Route . LoaderArgs ) {
const url = new URL ( request . url );
const searchQuery = url . searchParams . get ( "q" );
const cookie = request . headers . get ( "Cookie" );
const session = await getSession ( cookie );
const results = await searchProducts ({
query: searchQuery ,
userId: session . userId ,
category: params . category
});
return { results , query: searchQuery };
}
Using Context
// Server adapter setup
export default {
async fetch ( request , env ) {
return handleRequest ( request , {
getLoadContext : () => ({ env })
});
}
} ;
// Route module
export async function loader ({ context } : Route . LoaderArgs ) {
// Access Cloudflare env, Express req/res, etc.
const data = await context . env . DB . query ( ... );
return { data };
}
Best Practices
Type safety with Route.LoaderArgs
Use the route-specific type for automatic param type inference: // ✅ Types are inferred from your route config
export async function loader ({ params } : Route . LoaderArgs ) {
params . teamId ; // string (autocompleted)
}
// ❌ Generic types require manual annotation
export async function loader ({ params } : LoaderFunctionArgs ) {
params . teamId ; // unknown
}
Throw responses or errors to trigger error boundaries: export async function loader ({ params } : Route . LoaderArgs ) {
const product = await db . product . findUnique ({
where: { id: params . productId }
});
if ( ! product ) {
throw new Response ( "Not Found" , { status: 404 });
}
return { product };
}
Avoid serialization issues
Only return JSON-serializable data: // ❌ Dates, functions, and class instances don't serialize
return { createdAt: new Date () };
// ✅ Convert to strings
return { createdAt: new Date (). toISOString () };
Use Promise.all() to load data in parallel: export async function loader ({ params } : Route . LoaderArgs ) {
const [ user , posts , comments ] = await Promise . all ([
fetchUser ( params . userId ),
fetchPosts ( params . userId ),
fetchComments ( params . userId )
]);
return { user , posts , comments };
}
See Also