1---
2title: React Router
3description: Deploy React Router v7 framework apps to Prisma Compute with Prisma.Website.ReactRouter — SSR on Bun and native Vite dev locally.
4sidebar:
5 order: 7
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks @vercel/nft";
11
12`Prisma.Website.ReactRouter` builds [React Router](https://reactrouter.com)
13v7 in framework mode through your project's Vite pipeline. The shared
14Node target wraps the server manifest with `createRequestHandler` and
15serves client assets first. The output runs on **Bun in Prisma Compute**
16from a `tar.gz` upload, without a Docker image or registry.
17
18## Install
19
20Install the build-time integration; the resource loads `/react-router`
21and `/react-router/node` from your project:
22
23<Tabs syncKey="pkgManager">
24 <TabItem label="bun" icon="bun"><Code code={`bun add -d ${devDeps}`} lang="sh" /></TabItem>
25 <TabItem label="npm" icon="npm"><Code code={`npm install -D ${devDeps}`} lang="sh" /></TabItem>
26 <TabItem label="pnpm" icon="pnpm"><Code code={`pnpm add -D ${devDeps}`} lang="sh" /></TabItem>
27 <TabItem label="yarn" icon="seti:yarn"><Code code={`yarn add -D ${devDeps}`} lang="sh" /></TabItem>
28</Tabs>
29
30Keep the framework scaffold's `react-router`, `@react-router/dev`,
31`@react-router/node`, `isbot`, and `vite`. Put `@react-router/node` and
32`isbot` in `dependencies`: React Router reads them when choosing its
33default server entry and may try to install `isbot` if it is missing.
34
35## Configure Vite
36
37Keep the ordinary framework plugin and your Vite plugins:
38
39```typescript
40// vite.config.ts
41import { reactRouter } from "@react-router/dev/vite";
42import tailwindcss from "@tailwindcss/vite";
43import { defineConfig } from "vite";
44
45export default defineConfig({
46 plugins: [tailwindcss(), reactRouter()],
47});
48```
49
50No deployment adapter or server preset is needed.
51
52:::caution[Remove the Cloudflare Vite plugin]
53A Cloudflare scaffold targets workerd, not the Node-compatible output
54used by Prisma. Remove `@cloudflare/vite-plugin` from the config:
55
56```diff lang="typescript"
57-import { cloudflare } from "@cloudflare/vite-plugin";
58import { reactRouter } from "@react-router/dev/vite";
59import { defineConfig } from "vite";
60
61export default defineConfig({
62- plugins: [cloudflare({ viteEnvironment: { name: "ssr" } }), reactRouter()],
63+ plugins: [reactRouter()],
64});
65```
66
67:::
68
69## Declare the Website
70
71```typescript
72// alchemy.run.ts
73import * as Prisma from "alchemy/Prisma";
74
75export const Website = Prisma.Website.ReactRouter("Website", {
76 rootDir: "./web",
77});
78```
79
80`rootDir` holds `package.json`, `vite.config.*`, and
81`react-router.config.ts`; omit it for `.`. Live deploy creates a
82database-less project unless you pass `project`.
83
84## Add it to the Stack
85
86```typescript
87// alchemy.run.ts
88import * as Alchemy from "alchemy";
89import * as Effect from "effect/Effect";
90
91export default Alchemy.Stack(
92 "MyReactRouterSite",
93 { providers: Prisma.providers(), state: Alchemy.localState() },
94 Effect.gen(function* () {
95 const site = yield* Website;
96 return { url: site.url };
97 }),
98);
99```
100
101`site.url` is the Compute endpoint on deploy. Requests that miss client
102assets go to SSR routes, loaders, actions, and resource routes on Bun.
103
104## How the two build outputs are deployed
105
106Both `build/client` and `build/server` go into the uploaded artifact.
107The client directory contains browser bundles and `public/` files.
108`build/server/index.js` is a manifest, so Alchemy wraps it with React
109Router's request handler rather than executing it as a standalone server.
110
111A custom `buildDirectory` is read from the resolved config. If you set
112Vite's `base`, give React Router's `basename` the same prefix so emitted
113asset paths match the deployed files.
114
115## Add environment variables
116
117```diff lang="typescript"
118export const Website = Prisma.Website.ReactRouter("Website", {
119 rootDir: "./web",
120+ env: { API_BASE: "https://api.example.com" },
121});
122```
123
124Values are applied before build and dev and passed to Compute. `VITE_*`
125keys are inlined into browser code; keep secrets out of those keys.
126Outputs from sibling resources can be passed in `env` when declaring
127the Website inside your Stack generator.
128
129## Read the environment in a loader
130
131```typescript
132// app/routes/home.tsx
133export function loader() {
134 return { apiBase: process.env.API_BASE ?? "unset" };
135}
136```
137
138Loaders and actions run on Bun and read `process.env`.
139
140## Read the environment in a resource route
141
142```typescript
143// app/routes/api.hello.ts
144export function loader() {
145 return new Response(process.env.API_BASE ?? "unset");
146}
147```
148
149A route module without a default component is a resource route. It
150uses the same runtime environment.
151
152## Local dev
153
154`bun alchemy dev` runs React Router's Vite dev server with HMR; the
155Website creates no Prisma resources. Opt into live deployment during dev:
156
157```typescript
158export const Website = Prisma.Website.ReactRouter("Website").pipe(
159 Alchemy.remote(),
160);
161```
162
163## Custom domain
164
165```typescript
166const site = yield* Prisma.Website.ReactRouter("Web", {
167 domain: "app.example.com",
168});
169```
170
171`Prisma.CustomDomain` requires the app to be on the current default
172branch. Configure returned DNS records yourself and verify status before
173cutover; see [Custom domains](/prisma/frontend/websites#custom-domains).
174
175## Where next
176
177- [React Router API](/providers/prisma/website/reactrouter).
178- [React Router example](https://github.com/alchemy-run/alchemy/tree/main/examples/prisma-website-react-router).
179- [Websites](/prisma/frontend/websites) and [Compute apps](/prisma/compute/apps).