Overview
The @react-router/express package provides an Express.js request handler for React Router, allowing you to integrate React Router into existing Express applications.
Installation
npm install @react-router/express express
Basic Setup
Integrate React Router into your Express application:
import { createRequestHandler } from "@react-router/express";
import express from "express";
const app = express();
// React Router request handler
const reactRouterHandler = createRequestHandler({
build: await import("./build/server/index.js"),
});
// Serve static assets
app.use("/assets", express.static("build/client/assets"));
app.use(express.static("build/client"));
// Handle all routes with React Router
app.all("*", reactRouterHandler);
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
Development Mode
Use Vite’s middleware mode for development with hot module replacement:
import { createRequestHandler } from "@react-router/express";
import compression from "compression";
import express from "express";
import morgan from "morgan";
const viteDevServer =
process.env.NODE_ENV === "production"
? undefined
: await import("vite").then((vite) =>
vite.createServer({
server: { middlewareMode: true },
})
);
const reactRouterHandler = createRequestHandler({
build: viteDevServer
? () => viteDevServer.ssrLoadModule("virtual:react-router/server-build")
: await import("./build/server/index.js"),
});
const app = express();
app.use(compression());
app.disable("x-powered-by");
if (viteDevServer) {
app.use(viteDevServer.middlewares);
} else {
app.use(
"/assets",
express.static("build/client/assets", { immutable: true, maxAge: "1y" })
);
}
app.use(express.static("build/client", { maxAge: "1h" }));
app.use(morgan("tiny"));
app.all("*", reactRouterHandler);
const port = process.env.PORT || 3000;
app.listen(port, () =>
console.log(`Express server listening at http://localhost:${port}`)
);
Custom Load Context
Pass Express request and response objects to your routes:
const reactRouterHandler = createRequestHandler({
build: await import("./build/server/index.js"),
getLoadContext: (req, res) => ({
user: req.user,
session: req.session,
expressRequest: req,
expressResponse: res,
}),
});
Access the context in your routes:
import type { Route } from "./+types/dashboard";
export async function loader({ context }: Route.LoaderArgs) {
const user = context.user;
const session = context.session;
return { user };
}
Express Middleware
Use Express middleware before the React Router handler:
import cookieParser from "cookie-parser";
import session from "express-session";
import passport from "passport";
app.use(cookieParser());
app.use(
session({
secret: "s3cr3t",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.all("*", reactRouterHandler);
API Routes
Combine Express API routes with React Router:
// API routes
app.get("/api/health", (req, res) => {
res.json({ status: "ok" });
});
app.post("/api/webhook", express.json(), (req, res) => {
// Handle webhook
res.sendStatus(200);
});
// React Router handles all other routes
app.all("*", reactRouterHandler);
Error Handling
The request handler automatically passes errors to Express error handlers:
app.all("*", reactRouterHandler);
// Error handler
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send("Internal Server Error");
});
Request/Response Utilities
The package exports utilities for converting between Express and Web APIs:
import {
createRemixRequest,
createRemixHeaders,
sendRemixResponse,
} from "@react-router/express";
Convert Express headers to Web API Headers:
const headers = createRemixHeaders(req.headers);
createRemixRequest
Convert Express request to Web API Request:
const request = createRemixRequest(req, res);
sendRemixResponse
Send a Web API Response via Express:
const response = new Response("Hello", {
headers: { "Content-Type": "text/plain" },
});
await sendRemixResponse(res, response);
Trust Proxy
When deploying behind a proxy (nginx, load balancer, etc.), enable trust proxy:
app.set("trust proxy", 1);
This ensures req.hostname, req.protocol, and client IP are correctly detected.
Production Deployment
-
Build your application:
-
Set environment variable:
NODE_ENV=production node server.js
-
Use a process manager like PM2:
pm2 start server.js --name "my-app"
TypeScript Support
Add Express types:
npm install --save-dev @types/express
Type your load context:
import type { GetLoadContextFunction } from "@react-router/express";
interface AppLoadContext {
user: User | null;
session: Session;
}
const getLoadContext: GetLoadContextFunction = (req, res) => {
return {
user: req.user ?? null,
session: req.session,
};
};
Express middleware runs before React Router, allowing you to augment requests with authentication, sessions, and other context.