Skip to content

Commit

Permalink
docs: bring your own bundler
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanflorence committed Nov 21, 2024
1 parent 65c0e3b commit a02996d
Showing 1 changed file with 174 additions and 0 deletions.
174 changes: 174 additions & 0 deletions docs/start/framework/byob.md
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.

0 comments on commit a02996d

Please sign in to comment.