Declarative routing for React web applications
—
Components and utilities for server-side rendering including static routing, data loading on the server, and hydration support.
Router component for server-side rendering with a fixed location.
/**
* Router component for server-side rendering
* @param props - StaticRouter configuration options
* @returns Router component with static location
*/
function StaticRouter(props: StaticRouterProps): JSX.Element;
interface StaticRouterProps {
/** Base URL for all routes */
basename?: string;
/** Child routes and components */
children?: React.ReactNode;
/** Current location for rendering */
location: Partial<Location> | string;
}
interface Location {
pathname: string;
search: string;
hash: string;
state: any;
key: string;
}Usage Example:
import { StaticRouter } from "react-router-dom";
import { renderToString } from "react-dom/server";
// Server-side rendering
export function render(request: Request) {
const html = renderToString(
<StaticRouter location={request.url}>
<App />
</StaticRouter>
);
return `
<!DOCTYPE html>
<html>
<head><title>My App</title></head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`;
}Provider component for static routers with data loading capabilities.
/**
* Provider component for static routers with data support
* @param props - StaticRouterProvider configuration
* @returns Router provider with static handler context
*/
function StaticRouterProvider(props: StaticRouterProviderProps): JSX.Element;
interface StaticRouterProviderProps {
/** Router instance from createStaticRouter */
router: DataRouter;
/** Static handler context with data */
context: StaticHandlerContext;
/** Whether to hydrate on client */
hydrate?: boolean;
/** Nonce for CSP */
nonce?: string;
}
interface StaticHandlerContext {
/** Base URL path */
basename: string;
/** Current location */
location: Location;
/** Matched routes */
matches: StaticHandlerMatch[];
/** Data from route loaders */
loaderData: Record<string, any>;
/** Data from route actions */
actionData: Record<string, any> | null;
/** Route errors */
errors: Record<string, any> | null;
/** HTTP status code */
statusCode: number;
/** Response headers from loaders */
loaderHeaders: Record<string, Headers>;
/** Response headers from actions */
actionHeaders: Record<string, Headers> | null;
/** Deferred data promises */
activeDeferreds: Record<string, DeferredData> | null;
}Usage Example:
import {
createStaticRouter,
createStaticHandler,
StaticRouterProvider
} from "react-router-dom";
export async function render(request: Request) {
const handler = createStaticHandler(routes);
const context = await handler.query(request);
if (context instanceof Response) {
return context; // Handle redirects
}
const router = createStaticRouter(routes, context.location);
const html = renderToString(
<StaticRouterProvider router={router} context={context} />
);
return new Response(html, {
status: context.statusCode,
headers: { "Content-Type": "text/html" },
});
}Specialized router for React Server Components and framework-mode applications that hydrates from server state.
/**
* Framework-mode router for hydrating from server state
* @param props - HydratedRouter configuration options
* @returns Hydrated router component for SSR applications
*/
function HydratedRouter(props: HydratedRouterProps): JSX.Element;
interface HydratedRouterProps {
/** Context function for clientAction/clientLoader */
unstable_getContext?: RouterInit["unstable_getContext"];
/** Error handler for application errors */
unstable_onError?: (error: any, errorInfo: React.ErrorInfo) => void;
}
interface RouterInit {
/** Function to get context for client-side route functions */
unstable_getContext?: () => any;
}Usage Example:
import { HydratedRouter } from "react-router-dom";
// Client-side entry point for framework apps
function App() {
return (
<HydratedRouter
unstable_onError={(error, errorInfo) => {
console.error("App error:", error, errorInfo);
reportError(error);
}}
/>
);
}Server-side router component for React Server Components.
/**
* Server router component for React Server Components
* @param props - ServerRouter configuration
* @returns Server-side router for RSC applications
*/
function ServerRouter(props: ServerRouterProps): JSX.Element;
interface ServerRouterProps {
/** Current request context */
context: EntryContext;
/** Request URL */
url: string;
/** Abort signal for request cancellation */
abortDelay?: number;
/** Nonce for content security policy */
nonce?: string;
}
interface EntryContext {
/** Server manifest */
manifest: AssetsManifest;
/** Route modules */
routeModules: RouteModules;
/** Server build */
serverBuild: ServerBuild;
/** Static handler context */
staticHandlerContext: StaticHandlerContext;
}Components for managing document head and scripts in SSR applications.
/**
* Component for rendering document metadata
* @param props - Meta component options
* @returns Meta tags for document head
*/
function Meta(props?: { nonce?: string }): JSX.Element;
/**
* Component for rendering document links
* @param props - Links component options
* @returns Link tags for document head
*/
function Links(props?: LinksProps): JSX.Element;
/**
* Component for rendering application scripts
* @param props - Scripts component options
* @returns Script tags for document body
*/
function Scripts(props?: ScriptsProps): JSX.Element;
/**
* Component for prefetching page links
* @param props - PrefetchPageLinks options
* @returns Prefetch link tags
*/
function PrefetchPageLinks(props?: { page?: string }): JSX.Element;
interface LinksProps {
/** Nonce for CSP */
nonce?: string;
}
interface ScriptsProps {
/** Nonce for CSP */
nonce?: string;
/** Async script loading */
async?: boolean;
/** Cross-origin setting */
crossOrigin?: string;
}Usage Example:
import { Links, Meta, Scripts } from "react-router-dom";
function Document({ children, title }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>{title}</title>
<Meta />
<Links />
</head>
<body>
{children}
<Scripts />
</body>
</html>
);
}Server-side functions for generating metadata and handling requests.
/**
* Function for generating document metadata
*/
type MetaFunction<
Loader = unknown,
ParentsLoaders extends Record<string, unknown> = {}
> = (args: MetaArgs<Loader, ParentsLoaders>) => MetaDescriptor[];
interface MetaArgs<
Loader = unknown,
ParentsLoaders extends Record<string, unknown> = {}
> {
/** Route data from loader */
data: Loader;
/** Route parameters */
params: Params;
/** Current location */
location: Location;
/** Parent route data */
matches: UIMatch<unknown, unknown>[];
/** Error from route (if any) */
error?: unknown;
}
type MetaDescriptor =
| { title: string }
| { name: string; content: string }
| { property: string; content: string }
| { httpEquiv: string; content: string }
| { charset: string }
| { [key: string]: string };
/**
* Function for generating document links
*/
type LinksFunction = () => LinkDescriptor[];
type LinkDescriptor =
| PageLinkDescriptor
| HtmlLinkDescriptor;
interface PageLinkDescriptor {
page: string;
[key: string]: unknown;
}
interface HtmlLinkDescriptor {
rel: string;
href?: string;
[key: string]: unknown;
}
/**
* Function for generating response headers
*/
type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit;
interface HeadersArgs {
loaderHeaders: Headers;
parentHeaders: Headers;
actionHeaders: Headers;
errorHeaders: Headers | undefined;
}Usage Examples:
// Route with metadata
export const meta: MetaFunction<typeof loader> = ({ data, location }) => {
return [
{ title: data.post.title },
{
name: "description",
content: data.post.excerpt
},
{
property: "og:title",
content: data.post.title,
},
{
property: "og:url",
content: `https://example.com${location.pathname}`,
},
];
};
// Route with links
export const links: LinksFunction = () => {
return [
{ rel: "stylesheet", href: "/styles/post.css" },
{ rel: "prefetch", href: "/api/related-posts" },
];
};
// Route with headers
export const headers: HeadersFunction = ({ loaderHeaders }) => {
return {
"Cache-Control": "public, max-age=3600",
"Vary": "Accept-Encoding",
...Object.fromEntries(loaderHeaders.entries()),
};
};Functions that run on the client for enhanced interactivity.
/**
* Client-side loader function
*/
type ClientLoaderFunction<T = any> = (
args: ClientLoaderFunctionArgs
) => Promise<T> | T;
interface ClientLoaderFunctionArgs {
request: Request;
params: Params;
serverLoader: () => Promise<any>;
}
/**
* Client-side action function
*/
type ClientActionFunction<T = any> = (
args: ClientActionFunctionArgs
) => Promise<T> | T;
interface ClientActionFunctionArgs {
request: Request;
params: Params;
serverAction: () => Promise<any>;
}Usage Examples:
// Client loader that enhances server data
export const clientLoader: ClientLoaderFunction = async ({
serverLoader,
params
}) => {
const serverData = await serverLoader();
// Add client-specific data
const clientData = {
...serverData,
clientTimestamp: Date.now(),
isOnline: navigator.onLine,
};
return clientData;
};
// Client action with optimistic updates
export const clientAction: ClientActionFunction = async ({
request,
serverAction
}) => {
const formData = await request.formData();
// Optimistic update
updateUIOptimistically(formData);
try {
return await serverAction();
} catch (error) {
// Revert optimistic update
revertOptimisticUpdate();
throw error;
}
};Utilities for testing server-side rendered components.
/**
* Create route stub for testing
* @param routes - Route definitions for testing
* @param opts - Test configuration options
* @returns Component for testing routes
*/
function createRoutesStub(
routes: RouteObject[],
opts?: {
basename?: string;
initialEntries?: string[];
initialIndex?: number;
}
): React.ComponentType<RoutesTestStubProps>;
interface RoutesTestStubProps {
children?: React.ReactNode;
}Usage Example:
import { createRoutesStub } from "react-router-dom";
import { render, screen } from "@testing-library/react";
const RouteStub = createRoutesStub([
{
path: "/users/:id",
element: <UserProfile />,
loader: ({ params }) => ({ id: params.id, name: "John Doe" }),
},
]);
test("renders user profile", async () => {
render(<RouteStub initialEntries={["/users/123"]} />);
expect(await screen.findByText("John Doe")).toBeInTheDocument();
});// server.ts
import { createStaticHandler, createStaticRouter } from "react-router-dom";
const handler = createStaticHandler(routes);
export async function handleRequest(request: Request) {
const context = await handler.query(request);
if (context instanceof Response) {
return context;
}
const router = createStaticRouter(routes, context.location);
const html = renderToString(
<StaticRouterProvider router={router} context={context} />
);
return new Response(
`<!DOCTYPE html><html><body>${html}</body></html>`,
{
status: context.statusCode,
headers: { "Content-Type": "text/html" },
}
);
}// Route with server data loading
export const loader = async ({ request, params }) => {
const user = await fetchUser(params.id);
const posts = await fetchUserPosts(params.id);
return json({ user, posts }, {
headers: {
"Cache-Control": "public, max-age=300",
},
});
};
// Client hydration
const router = createBrowserRouter(routes);
hydrateRoot(
document.getElementById("root"),
<RouterProvider router={router} />
);// Error boundary for SSR
function RootErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status} {error.statusText}</h1>
<p>{error.data}</p>
</div>
);
}
return (
<div>
<h1>Something went wrong!</h1>
<pre>{error.message}</pre>
</div>
);
}Install with Tessl CLI
npx tessl i tessl/npm-react-router-dom