Skip to content

Commit 9e2f92a

Browse files
authored
Feat: Add createStaticHandler for Router SSR support (#9013)
* Rework router internals to operate on Requests for SSR * Add createStaticRouter * Wire up DataStaticRouter to wirk with createStaticHandler * Add ssr example with data router * Add changeset * Ensure deferred data isn't active during SSR * Bump bundle size * Remove DataFunctionArgs.signal in favor of request.signal * Lift pendingNavigationController/request creation * Update changeset * Clean up example using random bumber instead of date * Handle fetch submit 405 errors * Update tests for aborted static query calls * update changelog * Update stub static router with new internal deferred prop * enhance abort signal handling via race * Remove stale import * Fix changeset * Fix lint warnings * expose createStaticHAndler through index.ts * Rename static router "state" to "context" * chore: remove no longer used boundaryId cancellation
1 parent 6b76331 commit 9e2f92a

24 files changed

+7024
-427
lines changed

Diff for: .changeset/ninety-spoons-suffer.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
"react-router": patch
3+
"react-router-dom": patch
4+
"@remix-run/router": patch
5+
---
6+
7+
feat: Add `createStaticRouter` for `@remix-run/router` SSR usage
8+
9+
**Notable changes:**
10+
11+
- `request` is now the driving force inside the router utils, so that we can better handle `Request` instances coming form the server (as opposed to `string` and `Path` instances coming from the client)
12+
- Removed the `signal` param from `loader` and `action` functions in favor of `request.signal`
13+
14+
**Example usage (Document Requests):**
15+
16+
```jsx
17+
// Create a static handler
18+
let { dataRoutes, query } = createStaticHandler({ routes });
19+
20+
// Perform a full-document query for the incoming Fetch Request. This will
21+
// execute the appropriate action/loaders and return either the state or a
22+
// Fetch Response in the case of redirects.
23+
let state = await query(fetchRequest);
24+
25+
// If we received a Fetch Response back, let our server runtime handle directly
26+
if (state instanceof Response) {
27+
throw state;
28+
}
29+
30+
// Otherwise, render our application providing the data routes and state
31+
let html = ReactDOMServer.renderToString(
32+
<React.StrictMode>
33+
<DataStaticRouter dataRoutes={dataRoutes} state={state} />
34+
</React.StrictMode>
35+
);
36+
37+
// Grab the hydrationData to send to the client for <DataBrowserRouter>
38+
let hydrationData = {
39+
loaderData: state.loaderData,
40+
actionData: state.actionData,
41+
errors: state.errors,
42+
};
43+
```
44+
45+
**Example usage (Data Requests):**
46+
47+
```jsx
48+
// Create a static route handler
49+
let { queryRoute } = createStaticHandler({ routes });
50+
51+
// Perform a single-route query for the incoming Fetch Request. This will
52+
// execute the appropriate singular action/loader and return either the raw
53+
// data or a Fetch Response
54+
let data = await queryRoute(fetchRequest);
55+
56+
// If we received a Fetch Response back, return it directly
57+
if (data instanceof Response) {
58+
return data;
59+
}
60+
61+
// Otherwise, construct a Response from the raw data (assuming json here)
62+
return new Response(JSON.stringify(data), {
63+
headers: {
64+
"Content-Type": "application/json; charset=utf-8",
65+
},
66+
});
67+
```

Diff for: .eslintignore

+3
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ node_modules/
33
/docs/api
44
examples/**/dist/
55
packages/**/dist/
6+
packages/react-router-dom/server.d.ts
7+
packages/react-router-dom/server.js
8+
packages/react-router-dom/server.mjs
69
tutorial/dist/

Diff for: examples/ssr-data-router/.stackblitzrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"installDependencies": true,
3+
"startCommand": "npm run dev"
4+
}

Diff for: examples/ssr-data-router/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
title: Data Router Server Rendering
3+
toc: false
4+
---
5+
6+
# Data Router Server-side Rendering Example
7+
8+
This example adds [server-side rendering](https://reactjs.org/docs/react-dom-server.html) (SSR) to our basic example using a data router.
9+
10+
With SSR, the server renders your app and sends real HTML to the browser instead of an empty HTML document with a bunch of `<script>` tags. After the browser loads the HTML and JavaScript from the server, React "hydrates" the HTML document using the same components it used to render the app on the server.
11+
12+
This example contains a server (see [server.js](server.js)) that can run in both development and production modes.
13+
14+
In the browser entry point (see [src/entry.client.tsx](src/entry.client.tsx)), we use React Router like we would traditionally do in a purely client-side app and render a `<DataBrowserRouter>` to provide routing context to the rest of the app. The main difference is that instead of using `ReactDOM.render()` to render the app, since the HTML was already sent by the server, all we need is `ReactDOM.hydrate()`.
15+
16+
On the server (see [src/entry.server.tsx](src/entry.server.tsx)), we create a static request handler using `createStaticHandler` and query for the incoming `Request` we get from Express (note that we convert the Express request to a Web Fetch Request). Once the router is finished with data loading, we use React Router's `<DataStaticRouter>` to render the app in the correct state.
17+
18+
## Preview
19+
20+
Open this example on [StackBlitz](https://stackblitz.com):
21+
22+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router/tree/main/examples/ssr-data-router?file=src/App.tsx)

Diff for: examples/ssr-data-router/index.html

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>React Router - Data Router SSR Example</title>
7+
<link rel="stylesheet" href="/src/index.css" />
8+
</head>
9+
<body>
10+
<div id="app"><!--app-html--></div>
11+
<!--app-scripts-->
12+
<script type="module" src="/src/entry.client.tsx"></script>
13+
</body>
14+
</html>

0 commit comments

Comments
 (0)