Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/react-router-nitro-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@workflow/nitro': patch
---

Clean up temporary Nitro Vite servers and Workflow build contexts after builds.
7 changes: 7 additions & 0 deletions docs/content/docs/v5/getting-started/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ related:
---

import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStack, Vite, Express, Nest, Fastify, Python } from "@/app/[lang]/(home)/components/frameworks";
import { SiReactrouter } from "@icons-pack/react-simple-icons";

<Cards>
<Card href="/docs/getting-started/next">
Expand All @@ -20,6 +21,12 @@ import { Next, Nitro, SvelteKit, Nuxt, Hono, Bun, AstroDark, AstroLight, TanStac
<span className="font-medium">Vite</span>
</div>
</Card>
<Card href="/docs/getting-started/react-router">
<div className="flex flex-col items-center justify-center gap-2">
<SiReactrouter className="size-16" />
<span className="font-medium">React Router</span>
</div>
</Card>
<Card href="/docs/getting-started/astro">
<div className="flex flex-col items-center justify-center gap-2">
<AstroLight className="size-16 dark:hidden" />
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/v5/getting-started/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"title": "Getting Started",
"pages": [
"next",
"react-router",
"astro",
"express",
"fastify",
Expand Down
33 changes: 33 additions & 0 deletions docs/content/docs/v5/getting-started/react-router/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: React Router
description: Run durable workflows in a React Router framework-mode app using Nitro.
type: overview
summary: Choose your React Router version and connect React Router, Nitro, and Workflow SDK.
related:
- /docs/getting-started/nitro
- /docs/getting-started/vite
- /docs/foundations/workflows-and-steps
---

React Router framework mode builds the browser application and its server-rendering code, but it still needs a server to receive requests. [Nitro](https://v3.nitro.build) provides that server. Workflow SDK integrates with Nitro to add the durable workflow routes and build artifacts.

The three pieces share one Vite build:

1. **React Router** builds your routes, loaders, actions, and browser assets.
2. **Nitro** runs the React Router request handler and any routes in `server/routes`.
3. **Workflow SDK** finds files with `"use workflow"` and `"use step"`, then adds its runtime routes to Nitro.

Choose the guide that matches your React Router major version:

<AutoCards />

<Callout>
These guides require **Nitro v3**. Nitro v2 does not provide the Vite
environment integration used by this setup.
</Callout>

## What the bridge does

The setup adds a small `server/ssr.ts` file. It turns React Router's generated server build into a standard Fetch API handler that Nitro can run. The Vite config then points Nitro's server and public output at the same `build` directory React Router uses.

This is configuration in your application, not a separate React Router adapter. Your React Router routes remain React Router routes, while Nitro owns the HTTP server and Workflow SDK uses Nitro's lifecycle and routing.
5 changes: 5 additions & 0 deletions docs/content/docs/v5/getting-started/react-router/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "React Router",
"pages": ["v7", "v8"],
"defaultOpen": true
}
237 changes: 237 additions & 0 deletions docs/content/docs/v5/getting-started/react-router/v7.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---
title: React Router v7
description: Add durable workflows to a React Router v7 framework-mode app using Nitro v3.
type: guide
summary: Enable the Vite Environment API and configure React Router v7, Nitro v3, and Workflow SDK.
prerequisites:
- /docs/getting-started/react-router
related:
- /docs/getting-started/nitro
- /docs/foundations/workflows-and-steps
---

This guide starts with an existing React Router v7 framework-mode app. It is verified with v7.18.1; if your config does not recognize `v8_viteEnvironmentApi`, update to the latest v7 release.

<Steps>

<Step>

## Install Nitro and Workflow SDK

<Tabs items={["npm", "pnpm", "bun", "yarn"]} defaultValue="pnpm">

<Tab value="npm">

```bash
npm install nitro workflow
```

</Tab>

<Tab value="pnpm">

```bash
pnpm add nitro workflow
```

</Tab>

<Tab value="bun">

```bash
bun add nitro workflow
```

</Tab>

<Tab value="yarn">

```bash
yarn add nitro workflow
```

</Tab>

</Tabs>

This integration requires Nitro v3.

</Step>

<Step>

## Enable the Vite Environment API

React Router v7 keeps the Vite Environment API behind a future flag. Enable the required flag and set an explicit build directory:

```typescript title="react-router.config.ts" lineNumbers
import type { Config } from "@react-router/dev/config";

export default {
ssr: true,
buildDirectory: "build", // [!code highlight]
future: {
v8_viteEnvironmentApi: true, // [!code highlight]
},
} satisfies Config;
```

</Step>

<Step>

## Create the React Router server handler

Create `server/ssr.ts`:

```typescript title="server/ssr.ts" lineNumbers
import { createRequestHandler } from "react-router";

export default {
fetch: createRequestHandler(
() => import("virtual:react-router/server-build"),
import.meta.env.MODE,
),
};
```

This adapts React Router's generated server build to the Fetch API handler Nitro expects.

</Step>

<Step>

## Configure Vite

Update `vite.config.ts`:

```typescript title="vite.config.ts" lineNumbers
import { reactRouter } from "@react-router/dev/vite";
import { nitro } from "nitro/vite";
import { defineConfig } from "vite";
import { workflow } from "workflow/vite";
import reactRouterConfig from "./react-router.config";

export default defineConfig({
plugins: [
reactRouter(),
nitro({
serverDir: "./server",
output: {
dir: reactRouterConfig.buildDirectory,
serverDir: `${reactRouterConfig.buildDirectory}/server`,
publicDir: `${reactRouterConfig.buildDirectory}/client`,
},
}),
workflow({ dirs: ["workflows"] }),
],
environments: {
ssr: {
build: {
rollupOptions: {
input: "./server/ssr.ts",
},
},
},
},
});
```

Keep `dirs: ["workflows"]` so subsequent builds do not scan generated files under `build`. Place `reactRouter()` before `nitro()` in the plugin array.

</Step>

<Step>

## Create a workflow

Create `workflows/greeting.ts`:

```typescript title="workflows/greeting.ts" lineNumbers
export async function greetingWorkflow(name: string) {
"use workflow";

return greet(name);
}

async function greet(name: string) {
"use step";

return `Hello, ${name}!`;
}
```

</Step>

<Step>

## Start the workflow from a Nitro route

Create `server/routes/api/greeting.post.ts`:

```typescript title="server/routes/api/greeting.post.ts" lineNumbers
import { defineHandler } from "nitro";
import { start } from "workflow/api";
import { greetingWorkflow } from "../../../workflows/greeting";

export default defineHandler(async (event) => {
const { name } = (await event.req.json()) as { name: string };
const run = await start(greetingWorkflow, [name]);

return { runId: run.runId };
});
```

React Router continues to handle your application routes. Nitro handles this server route at `POST /api/greeting`, as well as Workflow SDK's internal routes.

</Step>

<Step>

## Run the app

Start the development server:

```bash
pnpm vite dev
```

Then start a workflow:

```bash
curl -X POST \
-H "content-type: application/json" \
-d '{"name":"Workflow"}' \
http://localhost:3000/api/greeting
```

Build and start the production server:

```bash
pnpm vite build
node ./build/server/index.mjs
```

You can inspect local runs with `pnpm workflow web`.

</Step>

</Steps>

## Troubleshooting

### Vite reports an invalid SSR input or `path.replace is not a function`

Set `future.v8_viteEnvironmentApi` to `true` in `react-router.config.ts`.

### React Router pages return 404

Check that the `ssr` environment input points to `./server/ssr.ts`.

### A second build tries to compile files under `build/server`

Use `workflow({ dirs: ["workflows"] })`, remove the existing `build` directory once, and rebuild.

### `vite build` finishes output but does not exit

Use `workflow@5.0.0-beta.33` or later with Nitro v3.
Loading
Loading