Building and deploying
The npm run dev server is not for real visitors: it is slower and rebuilds code on the fly. Before publishing, the project is built into optimized files, which are then run by an ordinary Node.js server. Deployment means moving the built app to a server or hosting platform.
Building
npm run buildThe script runs react-router build. A build/ folder appears:
build/
├── client/ browser files: JS, CSS, images, prerendered HTML
└── server/
└── index.js the server part: loaders, actions, renderingRunning
npm startThe script runs react-router-serve ./build/server/index.js. It is a ready-made server from the @react-router/serve package: it serves files from build/client and passes other requests to the app. The site opens at http://localhost:3000; set the PORT environment variable for a different port.
On your own server
The server needs Node.js 20+ and the environment variables from the lesson about them. The steps:
npm ci
npm run build
SESSION_SECRET=... NODE_ENV=production npm startThe process is usually kept running with systemd or pm2, with Nginx and HTTPS in front. Remember that the blog.db database file must live where the next deployment will not wipe it.
Docker
The project template already contains a Dockerfile that builds and runs the app:
docker build -t blog .
docker run -p 3000:3000 -e SESSION_SECRET=long-secret blogThe site is available at http://localhost:3000. The image runs on any host that supports containers.
Adapters
react-router-serve fits simple cases. When the app has to live inside your own server or run on a special platform, you use an adapter — a package that connects the framework to that environment:
| Package | Purpose |
|---|---|
@react-router/node | The base for Node.js, other Node adapters build on it |
@react-router/express | The app inside your own Express server |
@react-router/cloudflare | Cloudflare Workers and Pages |
@react-router/architect | AWS Lambda via Architect |
For example, with Express the app is plugged in as an ordinary handler:
// server.js
import express from "express";
import { createRequestHandler } from "@react-router/express";
const app = express();
app.use(express.static("build/client"));
app.use(createRequestHandler({ build: await import("./build/server/index.js") }));
app.listen(3000);Vercel and Netlify have their own ways of connecting; create-react-router has ready templates for different platforms (the --template option), listed in the React Router documentation.
Common mistake
Uploading only the build/ folder to the server and running npm start. The built code still imports packages from node_modules — react, react-router, the database driver. Copy package.json and the lock file to the server too and run npm ci --omit=dev to install the production dependencies.