diff --git a/docs/start/framework/byob.md b/docs/start/framework/byob.md
new file mode 100644
index 0000000000..e4ed7da3b0
--- /dev/null
+++ b/docs/start/framework/byob.md
@@ -0,0 +1,174 @@
+---
+title: Bring Your Own Bundler
+---
+
+This document is not complete and may contain errors
+
+# 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 ``.
+
+```tsx
+import {
+ createBrowserRouter,
+ RouterProvider,
+} from "react-router";
+import { createRoot } from "react-dom/client";
+
+createRoot(document.getElementById("root")).render(
+
+);
+```
+
+### 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(
+
+ );
+
+ // 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(`${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.