React Router on Easel
Deploy full-stack React Router 7 and 8 applications to Easel with server rendering, streaming, loaders, actions, middleware, static prerendering, and SPA support.
Easel deploys React Router 7 and 8 applications in Framework Mode. Add one deployment preset, and Easel serves browser assets and prerendered pages from the CDN and runs server rendering, loaders, actions, and resource routes in Functions. For how the platform fits together, see How Easel works.
At a glance
| Support level | Production-ready |
| Supported versions | React Router 7 and 8 |
| Mode | Framework Mode |
| Rendering | Static, SPA, SSR, streaming, and prerendering |
| Server runtime | Node.js |
| Required preset | @vercel/react-router |
| Automatic detection | Yes |
| Most recently tested with | React Router 7.18.x |
Deploy a React Router application
Install the deployment preset:
Add it to react-router.config.ts:
The preset makes the build write the server entry Easel deploys. Then create a project and connect the Git repository, or deploy with the CLI:
Easel detects the project and runs react-router build. You can override the
install command, build command, root directory, and Node.js version in
build configuration.
React Router 7 and 8
React Router 8 is a non-breaking upgrade from 7, but it requires Node.js
22.22.0 or newer, React 19.2.7 or newer, Vite 7 or newer, and an ESM project.
It also turns the future.v8_* behaviors on by default. Existing React Router 7 applications
deploy without upgrading. Set the project's Node.js version to one that meets
React Router 8's baseline before you upgrade.
To upgrade, move to the latest React Router 7 release, enable the future.v8_*
flags and fix what they surface, then upgrade the React Router packages
together, remove the flags, and test in a preview deployment.
What Easel deploys
| React Router output | Easel resource |
|---|---|
| Browser JavaScript and CSS | CDN assets |
| Public files | CDN assets |
| Prerendered pages | CDN assets |
| Server-rendered routes | Easel Functions |
| Loaders and actions | Easel Functions |
| Resource routes | Easel Functions |
| Server middleware | Application function |
| SPA fallback | CDN routing configuration |
The preset exposes the route structure, so server bundles can be split by route instead of shipping the whole server application as one Function.
Supported features
| Feature | Support | Notes |
|---|---|---|
| Framework Mode | Supported | Uses the React Router Vite plugin |
| React Router 8 | Supported | Requires the React Router 8 runtime baseline |
| React Router 7 | Supported | Future flags may be required for newer behavior |
| Server-side rendering | Supported | Dynamic document requests run in Easel Functions |
| Client-side navigation | Supported | Data requests are handled by the application function |
| Loaders and actions | Supported | Handle data loading, mutations, and form submissions |
| Nested routes | Supported | Includes nested layouts and data loading |
| Route error boundaries | Supported | Framework error responses are preserved |
| Streaming | Supported | Includes Suspense and deferred route data |
| Resource routes | Supported | Return JSON, files, feeds, and other non-UI responses |
| Server middleware | Supported | Stable in v8. Requires a future flag in v7 |
| Client middleware | Supported | Runs in the browser during client navigations |
| Static prerendering | Supported | Generated pages deploy to the CDN |
| SPA mode | Supported | Deploys a client-rendered application with route fallback |
| HTTP redirects | Supported | Includes redirects from loaders, actions, and middleware |
| Response headers | Supported | Includes route headers exports |
| Cookie sessions | Supported | Cookie and Set-Cookie headers are preserved |
| File uploads | Supported | Subject to function limits |
| OpenTelemetry | Supported | Instrument server code with standard SDKs. See tracing |
| WebSockets | Not supported | Function routes use HTTP request-response semantics |
Loaders and actions
Server rendering is on by default in Framework Mode. A loader runs on the server and passes data to the component through generated route types:
A document request invokes the application Function and streams the rendered response. After hydration, client navigations fetch loader data from the same Function without rerendering the document. Actions handle form submissions the same way, and redirects, cookies, and error boundaries follow the framework's normal response flow.
Streaming
A loader can return unresolved promises, and React Router streams the document before they settle:
Easel forwards chunks as React Router produces them. The Function stays active until the stream closes.
Middleware
Server middleware runs inside the application Function before and after the matched route's handlers. Use it for authentication, request context, and response headers. It's separate from the Firewall, which runs before a Function is invoked.
Pass per-request data through context, not module-level variables. One Function instance handles many requests.
Middleware is on by default in React Router 8. React Router 7 releases that support it need the future flag:
Response caching
Routes set CDN behavior with standard Cache-Control headers from a headers
export:
Use private, no-store for responses that depend on the user or session. See
CDN caching for how Easel caches and keys responses.
Static prerendering
List routes to generate during the build:
Easel deploys the generated HTML to the CDN. Requests for prerendered routes don't invoke a Function.
SPA mode
Set ssr: false to deploy a client-rendered application:
Easel serves the build from the CDN and routes application paths to the SPA
entry document. Server loaders and actions aren't deployed. clientLoader and
clientAction still work. If the application uses React Router only in Data
Mode or Declarative Mode, follow the Vite guide
instead.
Sessions and environment variables
Cookie sessions from createCookieSessionStorage work through standard
Set-Cookie headers and need no server storage. Use a database for session
state that's too large for a cookie or must be revoked centrally.
Server code reads variables from process.env. Don't pass process.env to the
browser. Return an explicit allowlist of public values from the root loader
instead. See environment variables
for scopes and how changes apply.
Project configuration
| Setting | Default |
|---|---|
| Install command | Detected from the package manager |
| Build command | Package script or react-router build |
| Development command | Package script or react-router dev |
| Static assets | Detected from the framework build |
| Server output | Detected from the deployment preset |
| Node.js version | Project or platform default |
In a monorepo, set the root directory to the folder containing the
application's package.json, react-router.config.ts, Vite config, and routes.
See monorepos.
Keep using react-router dev locally, and use a
preview deployment to check the production server
bundle, caching, and streaming.
Known limitations
- WebSockets. Functions can't hold long-lived WebSocket connections. See Functions runtime.
- Custom Node.js servers. Easel deploys the Web API request handler the preset generates. An application that needs to own a persistent HTTP server, raw TCP sockets, or process-level connection handling needs a separate service.
Troubleshooting
The build fails after upgrading to React Router 8
Check the Node.js, React, and Vite versions against React Router 8's baseline,
and confirm the project is ESM. Then remove obsolete future.v8_* flags and
check that @vercel/react-router and your Vite plugins support v8.
Middleware doesn't run in React Router 7
Check that the installed release supports middleware and that
future.v8_middleware is on. A client navigation to a route with no server
loader or action doesn't reach the server, so server middleware doesn't run.
A loader runs locally but not in production
Check that ssr: false isn't set. SPA mode doesn't deploy server loaders or
actions.
Deep links return 404 in SPA mode
Build with React Router's SPA mode and the preset, not as a plain Vite static build. The framework output includes the fallback route Easel needs.
Compatibility policy
Easel tests this guide against the version in At a glance. Test new React Router releases in a preview deployment first. See the compatibility policy.