-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
65c0e3b
commit a02996d
Showing
1 changed file
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
--- | ||
title: Bring Your Own Bundler | ||
--- | ||
|
||
<docs-warning>This document is not complete and may contain errors</docs-warning> | ||
|
||
# Bring Your Own Bundler | ||
|
||
The framework features are enabled by runtime features of React React. Instead of using React Router's Vite plugin, you can bring your own bundler and server abstractions. | ||
|
||
## Client Rendering | ||
|
||
### 1. Create a Router | ||
|
||
The browser runtime API that enables route module APIs (loaders, actions, etc.) is `createBrowserRouter`. | ||
|
||
It takes an array of route objects that support loaders, actions, error boundaries and more. The React Router Vite plugin creates one of these from `routes.ts`, but you can create one manually (or with an abstraction) and use your own bundler. | ||
|
||
```tsx | ||
import { createBrowserRouter } from "react-router"; | ||
|
||
let router = createBrowserRouter([ | ||
{ | ||
path: "/", | ||
Component: Root, | ||
children: [ | ||
{ | ||
path: "shows/:showId", | ||
Component: Show, | ||
loader: ({ request, params }) => | ||
fetch(`/api/show/${params.id}.json`, { | ||
signal: request.signal, | ||
}), | ||
}, | ||
], | ||
}, | ||
]); | ||
``` | ||
|
||
### 2. Render the Router | ||
|
||
To render the router in the browser, use `<RouterProvider>`. | ||
|
||
```tsx | ||
import { | ||
createBrowserRouter, | ||
RouterProvider, | ||
} from "react-router"; | ||
import { createRoot } from "react-dom/client"; | ||
|
||
createRoot(document.getElementById("root")).render( | ||
<RouterProvider router={router} /> | ||
); | ||
``` | ||
|
||
### 3. Lazy Loading | ||
|
||
Routes can take most of their definition lazily with the `lazy` property. | ||
|
||
```tsx | ||
createBrowserRouter([ | ||
{ | ||
path: "/show/:showId", | ||
lazy: () => { | ||
let [loader, action, Component] = await Promise.all([ | ||
import("./show.action.js"), | ||
import("./show.loader.js"), | ||
import("./show.component.js"), | ||
]); | ||
return { loader, action, Component }; | ||
}, | ||
}, | ||
]); | ||
``` | ||
|
||
## Server Rendering | ||
|
||
To server render a custom setup, there are a few server APIs available for rendering an data loading. | ||
|
||
### 1. Define Your Routes | ||
|
||
Routes are the same kinds of objects on the server as the client. | ||
|
||
```tsx | ||
export default [ | ||
{ | ||
path: "/", | ||
Component: Root, | ||
children: [ | ||
{ | ||
path: "shows/:showId", | ||
Component: Show, | ||
loader: ({ params }) => { | ||
return db.loadShow(params.id); | ||
}, | ||
}, | ||
], | ||
}, | ||
]; | ||
``` | ||
|
||
### 2. Create a static handler | ||
|
||
Turn your routes into a request handler with `createStaticHandler`: | ||
|
||
```tsx | ||
import { createStaticHandler } from "react-router"; | ||
import routes from "./some-routes"; | ||
|
||
let { query, dataRoutes } = createStaticHandler(routes); | ||
``` | ||
|
||
### 3. Get Routing Context and Render | ||
|
||
React Router works with web fetch [Requests](https://developer.mozilla.org/en-US/docs/Web/API/Request), so if your server doesn't, you'll need to adapt whatever objects it uses to a web fetch `Request` object. | ||
|
||
This step assumes your server receives `Request` objects. | ||
|
||
```tsx | ||
import { renderToString } from "react-dom/server"; | ||
import { | ||
createStaticHandler, | ||
createStaticRouter, | ||
StaticRouterProvider, | ||
} from "react-router"; | ||
|
||
import routes from "./some-routes.js"; | ||
|
||
let { query, dataRoutes } = createStaticHandler(routes); | ||
|
||
export async function handler(request: Request) { | ||
// 1. run actions/loaders to get the routing context with `query` | ||
let context = await query(request); | ||
|
||
// If `query` returns a Response, send it raw (a route probably a redirected) | ||
if (context instanceof Response) { | ||
return context; | ||
} | ||
|
||
// 2. Create a static router for SSR | ||
let router = createStaticRouter(dataRoutes, context); | ||
|
||
// 3. Render everything with StaticRouterProvider | ||
let html = renderToString( | ||
<StaticRouterProvider | ||
router={router} | ||
context={context} | ||
/> | ||
); | ||
|
||
// Setup headers from action and loaders from deepest match | ||
let leaf = context.matches[context.matches.length - 1]; | ||
let actionHeaders = context.actionHeaders[leaf.route.id]; | ||
let loaderHeaders = context.loaderHeaders[leaf.route.id]; | ||
let headers = new Headers(actionHeaders); | ||
if (loaderHeaders) { | ||
for (let [key, value] of loaderHeaders.entries()) { | ||
headers.append(key, value); | ||
} | ||
} | ||
|
||
headers.set("Content-Type", "text/html; charset=utf-8"); | ||
|
||
// 4. send a response | ||
return new Response(`<!DOCTYPE html>${html}`, { | ||
status: context.statusCode, | ||
headers, | ||
}); | ||
} | ||
``` | ||
|
||
### 4. Hydrate in the browser | ||
|
||
This section is incomplete and will be updated, please refer to `HydratedRouter` source to see how React Router does this with the React Router Vite plugin. |