Skip to content

Commit 371337f

Browse files
brophdawg11joseph0926
authored andcommitted
Add docs for React Router App Server and adapters (#14144)
1 parent 5d02bf2 commit 371337f

File tree

4 files changed

+270
-1
lines changed

4 files changed

+270
-1
lines changed

docs/api/other-api/adapter.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
---
2+
title: "@react-router/{adapter}"
3+
---
4+
5+
# Server Adapters
6+
7+
## Official Adapters
8+
9+
Idiomatic React Router apps can generally be deployed anywhere because React Router adapts the server's request/response to the [Web Fetch API][web-fetch-api]. It does this through adapters. We maintain a few adapters:
10+
11+
- `@react-router/architect`
12+
- `@react-router/cloudflare`
13+
- `@react-router/express`
14+
15+
These adapters are imported into your server's entry and are not used inside your React Router app itself.
16+
17+
If you initialized your app with `npx create-react-router@latest` with something other than the built-in [React Router App Server][rr-serve] (`@react-router/serve`), you will note a `server/index.js` file that imports and uses one of these adapters.
18+
19+
<docs-info>If you're using the built-in React Router App Server, you don't interact with this API</docs-info>
20+
21+
Each adapter has the same API. In the future we may have helpers specific to the platform you're deploying to.
22+
23+
## `@react-router/express`
24+
25+
[Reference Documentation ↗](https://api.reactrouter.com/v7/modules/_react_router_express.html)
26+
27+
Here's an example with express:
28+
29+
```ts lines=[1-3,11-22]
30+
const {
31+
createRequestHandler,
32+
} = require("@react-router/express");
33+
const express = require("express");
34+
35+
const app = express();
36+
37+
// needs to handle all verbs (GET, POST, etc.)
38+
app.all(
39+
"*",
40+
createRequestHandler({
41+
// `react-router build` and `react-router dev` output files to a build directory,
42+
// you need to pass that build to the request handler
43+
build: require("./build"),
44+
45+
// Return anything you want here to be available as `context` in your
46+
// loaders and actions. This is where you can bridge the gap between your
47+
// server and React Router
48+
getLoadContext(req, res) {
49+
return {};
50+
},
51+
}),
52+
);
53+
```
54+
55+
### Migrating from the React Router App Server
56+
57+
If you started an app with the [React Router App Server][rr-serve] but find that you want to take control over the Express server and customize it, it should be fairly straightforward to migrate way from `@react-router/serve`.
58+
59+
You can refer to the [Express template][express-template] as a reference, but here are the main changes you will need to make:
60+
61+
**1. Update deps**
62+
63+
```sh
64+
npm uninstall @react-router/serve
65+
npm install @react-router/express compression express morgan cross-env
66+
npm install --save-dev @types/express @types/express-serve-static-core @types/morgan
67+
```
68+
69+
**2. Add a server**
70+
71+
Create your React Router express server in `server/app.ts`:
72+
73+
```ts filename=server/app.ts
74+
import "react-router";
75+
import { createRequestHandler } from "@react-router/express";
76+
import express from "express";
77+
78+
export const app = express();
79+
80+
app.use(
81+
createRequestHandler({
82+
build: () =>
83+
import("virtual:react-router/server-build"),
84+
}),
85+
);
86+
```
87+
88+
Copy the [`server.js`][express-template-server-js] into your app. This is the boilerplate setup we recommend to allow the same server code to run both the development and production builds of your app. Two separate files are used here so that the main Express server code can be written in TypeScript (`server/app.ts`) and compiled into your server build by React Router, and then executed via `node server.js`.
89+
90+
**3. Update `vite.config.ts` to compile the server**
91+
92+
```tsx filename=vite.config.ts lines=[6-10]
93+
import { reactRouter } from "@react-router/dev/vite";
94+
import { defineConfig } from "vite";
95+
import tsconfigPaths from "vite-tsconfig-paths";
96+
97+
export default defineConfig(({ isSsrBuild }) => ({
98+
build: {
99+
rollupOptions: isSsrBuild
100+
? { input: "./server/app.ts" }
101+
: undefined,
102+
},
103+
plugins: [reactRouter(), tsconfigPaths()],
104+
}));
105+
```
106+
107+
**4. Update `package.json` scripts**
108+
109+
Update the `dev` and `start` scripts to use your new Express server:
110+
111+
```json filename=package.json
112+
{
113+
// ...
114+
"scripts": {
115+
"dev": "cross-env NODE_ENV=development node server.js",
116+
"start": "node server.js"
117+
// ...
118+
}
119+
// ...
120+
}
121+
```
122+
123+
## `@react-router/cloudflare`
124+
125+
[Reference Documentation ↗](https://api.reactrouter.com/v7/modules/_react_router_cloudflare.html)
126+
127+
Here's an example with Cloudflare:
128+
129+
```ts
130+
import { createRequestHandler } from "react-router";
131+
132+
declare module "react-router" {
133+
export interface AppLoadContext {
134+
cloudflare: {
135+
env: Env;
136+
ctx: ExecutionContext;
137+
};
138+
}
139+
}
140+
141+
const requestHandler = createRequestHandler(
142+
() => import("virtual:react-router/server-build"),
143+
import.meta.env.MODE,
144+
);
145+
146+
export default {
147+
async fetch(request, env, ctx) {
148+
return requestHandler(request, {
149+
cloudflare: { env, ctx },
150+
});
151+
},
152+
} satisfies ExportedHandler<Env>;
153+
```
154+
155+
## `@react-router/node`
156+
157+
While not a direct "adapter" like the above, this package contains utilities for working with Node-based adapters.
158+
159+
[Reference Documentation ↗](https://api.reactrouter.com/v7/modules/_react_router_node.html)
160+
161+
### Node Version Support
162+
163+
React Router officially supports **Active** and **Maintenance** [Node LTS versions][node-releases] at any given point in time. Dropped support for End of Life Node versions is done in a React Router Minor release.
164+
165+
[node-releases]: https://nodejs.org/en/about/previous-releases
166+
[web-fetch-api]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
167+
[rr-serve]: ./serve
168+
[express-template]: https://github.com/remix-run/react-router-templates/tree/main/node-custom-server
169+
[express-template-server-js]: https://github.com/remix-run/react-router-templates/blob/main/node-custom-server/server.js

docs/api/other-api/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: Other API
3+
order: 9
4+
---

docs/api/other-api/serve.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
title: "@react-router/serve"
3+
---
4+
5+
# React Router App Server
6+
7+
React Router is designed for you to own your server, but if you don't want to set one up, you can use the React Router App Server instead. It's a production-ready but basic Node.js server built with [Express][express].
8+
9+
By design, we do not provide options to customize the React Router App Server because if you need to customize the underlying `express` server, we'd rather you manage the server completely instead of creating an abstraction to handle all the possible customizations you may require. If you find you want to customize it, you can [migrate to the `@react-router/express` adapter][eject].
10+
11+
You can see the underlying `express` server configuration in [packages/react-router-serve/cli.ts][rr-serve-code]. By default, it uses the following Express middlewares (please refer to their documentation for default behaviors):
12+
13+
- [`compression`][compression]
14+
- [`express.static`][express-static] (and thus [`serve-static`][serve-static])
15+
- [`morgan`][morgan]
16+
17+
## `HOST` environment variable
18+
19+
You can configure the hostname for your Express app via `process.env.HOST` and that value will be passed to the internal [`app.listen`][express-listen] method when starting the server.
20+
21+
```shellscript nonumber
22+
HOST=127.0.0.1 npx react-router-serve build/index.js
23+
```
24+
25+
```shellscript nonumber
26+
react-router-serve <server-build-path>
27+
# e.g.
28+
react-router-serve build/index.js
29+
```
30+
31+
## `PORT` environment variable
32+
33+
You can change the port of the server with an environment variable.
34+
35+
```shellscript nonumber
36+
PORT=4000 npx react-router-serve build/index.js
37+
```
38+
39+
## Development Environment
40+
41+
Depending on `process.env.NODE_ENV`, the server will boot in development or production mode.
42+
43+
The `server-build-path` needs to point to the `serverBuildPath` defined in `react-router.config.ts`.
44+
45+
Because only the build artifacts (`build/`, `public/build/`) need to be deployed to production, the `react-router.config.ts` is not guaranteed to be available in production, so you need to tell React Router where your server build is with this option.
46+
47+
In development, `react-router-serve` will ensure the latest code is run by purging the `require` cache for every request. This has some effects on your code you might need to be aware of:
48+
49+
- Any values in the module scope will be "reset"
50+
51+
```tsx lines=[1-3]
52+
// this will be reset for every request because the module cache was
53+
// cleared and this will be required brand new
54+
const cache = new Map();
55+
56+
export async function loader({ params }) {
57+
if (cache.has(params.foo)) {
58+
return cache.get(params.foo);
59+
}
60+
61+
const record = await fakeDb.stuff.find(params.foo);
62+
cache.set(params.foo, record);
63+
return record;
64+
}
65+
66+
If you need a workaround for preserving cache in development, you can set up a [singleton][singleton] in your server.
67+
68+
```
69+
70+
- Any **module side effects** will remain in place! This may cause problems but should probably be avoided anyway.
71+
72+
```tsx lines=[1-4]
73+
// this starts running the moment the module is imported
74+
setInterval(() => {
75+
console.log(Date.now());
76+
}, 1000);
77+
78+
export async function loader() {
79+
// ...
80+
}
81+
```
82+
83+
If you need to write your code in a way that has these types of module side effects, you should set up your own [@react-router/express][rr-express] server and a tool in development like `pm2-dev` or `nodemon` to restart the server on file changes instead.
84+
85+
In production this doesn't happen. The server boots up, and that's the end of it.
86+
87+
[rr-express]: ./adapter#createrequesthandler
88+
[singleton]: ../guides/manual-mode#keeping-in-memory-server-state-across-rebuilds
89+
[express-listen]: https://expressjs.com/en/api.html#app.listen
90+
[rr-serve-code]: https://github.com/remix-run/react-router/blob/main/packages/react-router-serve/cli.ts
91+
[compression]: https://expressjs.com/en/resources/middleware/compression.html
92+
[express-static]: https://expressjs.com/en/4x/api.html#express.static
93+
[serve-static]: https://expressjs.com/en/resources/middleware/serve-static.html
94+
[morgan]: https://expressjs.com/en/resources/middleware/morgan.html
95+
[express]: https://expressjs.com/
96+
[eject]: (./adapter#migrating-from-the-react-router-app-server)

docs/api/utils/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
---
22
title: Utils
3+
order: 8
34
---
4-

0 commit comments

Comments
 (0)