UNPKG

5.1 kB Markdown View Raw
1---
2title: Custom Framework
3order: 8
4---
5
6# Custom Framework
7
8[MODES: data]
9
10## Introduction
11
12Instead of using `@react-router/dev`, you can integrate React Router's framework features (like loaders, actions, fetchers, etc.) into your own bundler and server abstractions with Data Mode.
13
14## Client Rendering
15
16### 1. Create a Router
17
18The browser runtime API that enables route module APIs (loaders, actions, etc.) is `createBrowserRouter`.
19
20It 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.
21
22```tsx
23import { createBrowserRouter } from "react-router";
24
25let router = createBrowserRouter([
26 {
27 path: "/",
28 Component: Root,
29 children: [
30 {
31 path: "shows/:showId",
32 Component: Show,
33 loader: ({ request, params }) =>
34 fetch(`/api/show/${params.showId}.json`, {
35 signal: request.signal,
36 }),
37 },
38 ],
39 },
40]);
41```
42
43### 2. Render the Router
44
45To render the router in the browser, use `<RouterProvider>`.
46
47```tsx
48import {
49 createBrowserRouter,
50 RouterProvider,
51} from "react-router";
52import { createRoot } from "react-dom/client";
53
54createRoot(document.getElementById("root")).render(
55 <RouterProvider router={router} />,
56);
57```
58
59### 3. Lazy Loading
60
61Routes can take most of their definition lazily with the `lazy` property.
62
63```tsx
64createBrowserRouter([
65 {
66 path: "/show/:showId",
67 lazy: {
68 loader: async () =>
69 (await import("./show.loader.js")).loader,
70 action: async () =>
71 (await import("./show.action.js")).action,
72 Component: async () =>
73 (await import("./show.component.js")).Component,
74 },
75 },
76]);
77```
78
79## Server Rendering
80
81To server render a custom setup, there are a few server APIs available for rendering and data loading.
82
83This guide simply gives you some ideas about how it works. For deeper understanding, please see the [Custom Framework Example Repo](https://github.com/remix-run/custom-react-router-framework-example)
84
85### 1. Define Your Routes
86
87Routes are the same kinds of objects on the server as the client.
88
89```tsx
90export default [
91 {
92 path: "/",
93 Component: Root,
94 children: [
95 {
96 path: "shows/:showId",
97 Component: Show,
98 loader: ({ params }) => {
99 return db.loadShow(params.id);
100 },
101 },
102 ],
103 },
104];
105```
106
107### 2. Create a static handler
108
109Turn your routes into a request handler with `createStaticHandler`:
110
111```tsx
112import { createStaticHandler } from "react-router";
113import routes from "./some-routes";
114
115let { query, dataRoutes } = createStaticHandler(routes);
116```
117
118### 3. Get Routing Context and Render
119
120React 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.
121
122This step assumes your server receives `Request` objects.
123
124```tsx
125import { renderToString } from "react-dom/server";
126import {
127 createStaticHandler,
128 createStaticRouter,
129 StaticRouterProvider,
130} from "react-router";
131
132import routes from "./some-routes.js";
133
134let { query, dataRoutes } = createStaticHandler(routes);
135
136export async function handler(request: Request) {
137 // 1. run actions/loaders to get the routing context with `query`
138 let context = await query(request);
139
140 // If `query` returns a Response, send it raw (a route probably a redirected)
141 if (context instanceof Response) {
142 return context;
143 }
144
145 // 2. Create a static router for SSR
146 let router = createStaticRouter(dataRoutes, context);
147
148 // 3. Render everything with StaticRouterProvider
149 let html = renderToString(
150 <StaticRouterProvider
151 router={router}
152 context={context}
153 />,
154 );
155
156 // Setup headers from action and loaders from deepest match
157 let leaf = context.matches[context.matches.length - 1];
158 let actionHeaders = context.actionHeaders[leaf.route.id];
159 let loaderHeaders = context.loaderHeaders[leaf.route.id];
160 let headers = new Headers(actionHeaders);
161 if (loaderHeaders) {
162 for (let [key, value] of loaderHeaders.entries()) {
163 headers.append(key, value);
164 }
165 }
166
167 headers.set("Content-Type", "text/html; charset=utf-8");
168
169 // 4. send a response
170 return new Response(`<!DOCTYPE html>${html}`, {
171 status: context.statusCode,
172 headers,
173 });
174}
175```
176
177### 4. Hydrate in the browser
178
179Hydration data is embedded onto `window.__staticRouterHydrationData`, use that to initialize your client side router and render a `<RouterProvider>`.
180
181```tsx
182import { StrictMode } from "react";
183import { hydrateRoot } from "react-dom/client";
184import { RouterProvider } from "react-router/dom";
185import routes from "./app/routes.js";
186import { createBrowserRouter } from "react-router";
187
188let router = createBrowserRouter(routes, {
189 hydrationData: window.__staticRouterHydrationData,
190});
191
192hydrateRoot(
193 document,
194 <StrictMode>
195 <RouterProvider router={router} />
196 </StrictMode>,
197);
198```