Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
409f192
Add React Router plugin performance benchmarks
ScriptedAlchemy Jun 16, 2026
7b7ae1a
Summarize plugin benchmark operations
ScriptedAlchemy Jun 16, 2026
aeae550
Optimize route export analysis caches
ScriptedAlchemy Jun 16, 2026
c39f5fa
Fix SPA benchmark fixture generation
ScriptedAlchemy Jun 16, 2026
c2452de
perf: reduce benchmark and route analysis overhead
ScriptedAlchemy Jun 17, 2026
969e671
perf: reduce route analysis overhead
ScriptedAlchemy Jun 17, 2026
55dfd27
Cache combined route export analysis
ScriptedAlchemy Jun 17, 2026
60f8028
perf: narrow route transform environments
ScriptedAlchemy Jun 17, 2026
ea32619
Replace Babel and esbuild with Yuku
hardfist Jun 17, 2026
aa33f69
Preserve parens in Yuku transforms
hardfist Jun 17, 2026
9d2e7a6
Keep JSX component references during DCE
hardfist Jun 17, 2026
071f99f
perf: reduce route artifact build overhead
ScriptedAlchemy Jun 17, 2026
4e97c1d
perf: simplify route artifact helpers
ScriptedAlchemy Jun 17, 2026
338e32c
perf: clean up route artifact helpers
ScriptedAlchemy Jun 17, 2026
192fa2a
Merge remote-tracking branch 'origin/main' into perf/bundling-perform…
ScriptedAlchemy Jun 17, 2026
9887c23
Merge remote-tracking branch 'origin/main' into yuku-replace-babel-es…
ScriptedAlchemy Jun 17, 2026
f1b6c05
Merge remote-tracking branch 'origin/yuku-replace-babel-esbuild' into…
ScriptedAlchemy Jun 18, 2026
deb4674
perf: simplify yuku route analysis cleanup
ScriptedAlchemy Jun 18, 2026
fd8849c
perf: clear build request stream timeouts
ScriptedAlchemy Jun 18, 2026
dcdd514
chore: add performance changeset
ScriptedAlchemy Jun 18, 2026
f28c70b
perf: reduce route analysis and chunk overhead (#42)
ScriptedAlchemy Jun 18, 2026
f69bc2e
perf: enable lazy compilation by default
ScriptedAlchemy Jun 18, 2026
0711614
fix: keep lazy compilation opt-in
ScriptedAlchemy Jun 18, 2026
fe68c84
chore: clarify benchmark lazy compilation option
ScriptedAlchemy Jun 18, 2026
59f88b8
fix: restart dev server for route entry changes (#43)
ScriptedAlchemy Jun 19, 2026
9a128d7
fix: harden route export splitting
ScriptedAlchemy Jun 19, 2026
f3a2633
feat: add parallel route transform executor
ScriptedAlchemy Jun 19, 2026
45ab9bc
Remove planning artifacts from bundling performance branch (#44)
ScriptedAlchemy Jun 19, 2026
149325c
Tune split route transform worker cap
ScriptedAlchemy Jun 19, 2026
3614371
Remove default route transform worker caps
ScriptedAlchemy Jun 19, 2026
061b9a0
Default concurrency to available cores minus two
ScriptedAlchemy Jun 19, 2026
adb6b57
chore: simplify bundling performance branch
ScriptedAlchemy Jun 19, 2026
13dd705
chore: share bounded cache helper
ScriptedAlchemy Jun 19, 2026
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/bright-routes-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rsbuild-plugin-react-router": patch
---

Improve route analysis and route chunking performance for larger applications, with benchmark tooling to track build overhead.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ build
.unpack-cache/
.codex/
.tracedecay/
.benchmark/
task/upstream/
task/output/

Expand Down
123 changes: 92 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ A Rsbuild plugin that provides seamless integration with React Router, supportin

## Features


- 🚀 Zero-config setup with sensible defaults
- 🔄 Automatic route generation from file system
- 🖥️ Server-Side Rendering (SSR) support
Expand Down Expand Up @@ -58,11 +57,11 @@ export default defineConfig(() => {
// Optional: Enable custom server mode
customServer: false,
// Optional: Specify server output format
serverOutput: "commonjs",
serverOutput: 'commonjs',
// Optional: enable experimental support for module federation
federation: false
}),
pluginReact()
federation: false,
}),
pluginReact(),
],
};
});
Expand All @@ -73,6 +72,7 @@ export default defineConfig(() => {
The plugin uses a two-part configuration system:

1. **Plugin Options** (in `rsbuild.config.ts`):

```ts
pluginReactRouter({
/**
Expand All @@ -87,7 +87,27 @@ pluginReactRouter({
* Options: "commonjs" | "module"
* @default "module"
*/
serverOutput?: "commonjs" | "module"
serverOutput?: "commonjs" | "module",

/**
* Rsbuild dev-only lazy compilation behavior.
* @default false
*/
lazyCompilation?: boolean | Rspack.LazyCompilationOptions,

/**
* Emit structured React Router plugin timing logs.
* @default false
*/
logPerformance?: boolean,

/**
* Run route transforms in a worker-thread pool.
* Pass `false` to disable or `{ maxWorkers }` to override the default worker count.
* @default true, using `available CPUs - 2` workers.
*/
parallelTransforms?: boolean | { maxWorkers?: number },

/**
* Enable experimental support for module federation
* @default false
Expand All @@ -106,6 +126,7 @@ passing the build to React Router's request handler.
```

2. **React Router Configuration** (in `react-router.config.*`):

```ts
import type { Config } from '@react-router/dev/config';

Expand All @@ -120,19 +141,19 @@ export default {
* The file name for the server build output.
* @default "index.js"
*/
serverBuildFile: "index.js",
serverBuildFile: 'index.js',

/**
* The output format for the server build.
* Options: "esm" | "cjs"
* @default "esm"
*/
serverModuleFormat: "esm",
serverModuleFormat: 'esm',

/**
* Split server bundles by route branch (advanced).
*/
serverBundles: async ({ branch }) => branch[0]?.id ?? "main",
serverBundles: async ({ branch }) => branch[0]?.id ?? 'main',

/**
* Hook called after the build completes.
Expand Down Expand Up @@ -256,13 +277,14 @@ export default {
} satisfies Config;
```

For large sites, you can tune prerender concurrency:
For large sites, prerendering defaults to `availableParallelism - 2` concurrent
paths. You can tune prerender concurrency:

```ts
export default {
ssr: false,
prerender: {
paths: ['/','/about'],
paths: ['/', '/about'],
unstable_concurrency: 4,
},
} satisfies Config;
Expand All @@ -275,7 +297,12 @@ If no configuration is provided, the following defaults will be used:
```ts
// Plugin defaults (rsbuild.config.ts)
{
customServer: false
customServer: false,
serverOutput: 'module',
federation: false,
lazyCompilation: false,
logPerformance: false,
parallelTransforms: true // adaptive worker pool
}

// Router defaults (react-router.config.ts)
Expand All @@ -287,6 +314,18 @@ If no configuration is provided, the following defaults will be used:
}
```

`parallelTransforms: true` uses worker threads for route builds. The default
worker count is `availableParallelism - 2`. Pass `{ maxWorkers }` to override
that count, or `false` to run route transforms inline.

For builds with 256+ routes, detailed file-size reporting is compacted to totals
by default to avoid gzipping and printing thousands of assets. Set
`performance.printFileSize` to an object to customize that output.

Route transform source maps are generated in development only. If you enable
Rsbuild source maps for faster local debugging, prefer a cheap JS map:
`output.sourceMap: { js: 'cheap-module-source-map', css: false }`.

### Route Configuration

Routes can be defined in `app/routes.ts` using the helper functions from `@react-router/dev/routes`:
Expand Down Expand Up @@ -326,6 +365,7 @@ export default [
```

The plugin provides several helper functions for defining routes:

- `index()` - Creates an index route
- `route()` - Creates a regular route with a path
- `layout()` - Creates a layout route with nested children
Expand All @@ -336,6 +376,7 @@ The plugin provides several helper functions for defining routes:
Route components support the following exports:

#### Client-side Exports

- `default` - The route component
- `ErrorBoundary` - Error boundary component
- `HydrateFallback` - Loading component during hydration
Expand All @@ -349,6 +390,7 @@ Route components support the following exports:
- `shouldRevalidate` - Revalidation control

#### Server-side Exports

- `loader` - Server-side data loading
- `action` - Server-side form actions
- `middleware` - Server-side middleware
Expand Down Expand Up @@ -387,9 +429,9 @@ export default defineConfig(() => {
return {
plugins: [
pluginReactRouter({
customServer: true
}),
pluginReact()
customServer: true,
}),
pluginReact(),
],
};
});
Expand All @@ -398,6 +440,7 @@ export default defineConfig(() => {
When using a custom server, you'll need to:

1. Create a server handler (`server/index.ts`):

```ts
import { createRequestHandler } from '@react-router/express';

Expand All @@ -413,6 +456,7 @@ export const app = createRequestHandler({
```

2. Set up your server entry point (`server.js`):

```js
import { createRsbuild, loadConfig } from '@rsbuild/core';
import express from 'express';
Expand Down Expand Up @@ -451,9 +495,11 @@ async function startServer() {
devServer.connectWebSocket({ server });
} else {
// Production mode
app.use(express.static(path.join(__dirname, 'build/client'), {
index: false
}));
app.use(
express.static(path.join(__dirname, 'build/client'), {
index: false,
})
);

// Load the server bundle
const serverBundle = await import('./build/server/static/js/app.js');
Expand All @@ -477,6 +523,7 @@ startServer().catch(console.error);
```

3. Update your `package.json` scripts:

```json
{
"scripts": {
Expand All @@ -488,6 +535,7 @@ startServer().catch(console.error);
```

The custom server setup allows you to:

- Add custom middleware
- Handle API routes
- Integrate with databases
Expand All @@ -500,6 +548,7 @@ The custom server setup allows you to:
To deploy your React Router app to Cloudflare Workers:

1. **Configure Rsbuild** (`rsbuild.config.ts`):

```ts
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
Expand All @@ -524,17 +573,24 @@ export default defineConfig({
module: true,
},
resolve: {
conditionNames: ['workerd', 'worker', 'browser', 'import', 'require'],
conditionNames: [
'workerd',
'worker',
'browser',
'import',
'require',
],
},
},
},
},
},
plugins: [pluginReactRouter({customServer: true}), pluginReact()],
plugins: [pluginReactRouter({ customServer: true }), pluginReact()],
});
```

2. **Configure Wrangler** (`wrangler.toml`):

```toml
workers_dev = true
name = "my-react-router-worker"
Expand All @@ -552,6 +608,7 @@ VALUE_FROM_CLOUDFLARE = "Hello from Cloudflare"
```

3. **Create Worker Entry** (`server/index.ts`):

```ts
import { createRequestHandler } from 'react-router';

Expand Down Expand Up @@ -588,6 +645,7 @@ export default {
```

4. **Update Package Dependencies**:

```json
{
"dependencies": {
Expand All @@ -605,6 +663,7 @@ export default {
```

5. **Setup Deployment Scripts** (`package.json`):

```json
{
"scripts": {
Expand All @@ -630,6 +689,7 @@ export default {
### Development Workflow:

1. Local Development:

```bash
# Start local development server
npm run dev
Expand All @@ -646,6 +706,7 @@ export default {
## Development

The plugin automatically:

- Runs type generation during development and build
- Sets up development server with live reload
- Handles route-based code splitting
Expand All @@ -667,17 +728,17 @@ CSS endpoint) are not supported 1:1.

The repository includes several examples demonstrating different use cases:

| Example | Description | Port | Command |
|---------|-------------|------|---------|
| [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` |
| [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` |
| [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` |
| [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` |
| [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` |
| [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` |
| [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` |
| [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` |
| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` |
| Example | Description | Port | Command |
| ----------------------------------------------------------------------- | --------------------------------------- | ---- | ---------- |
| [default-template](./examples/default-template) | Standard SSR setup with React Router | 3000 | `pnpm dev` |
| [spa-mode](./examples/spa-mode) | Single Page Application (`ssr: false`) | 3001 | `pnpm dev` |
| [prerender](./examples/prerender) | Static prerendering for multiple routes | 3002 | `pnpm dev` |
| [custom-node-server](./examples/custom-node-server) | Custom Express server with SSR | 3003 | `pnpm dev` |
| [cloudflare](./examples/cloudflare) | Cloudflare Workers deployment | 3004 | `pnpm dev` |
| [client-only](./examples/client-only) | `.client` modules with SSR hydration | 3010 | `pnpm dev` |
| [epic-stack](./examples/epic-stack) | Full-featured Epic Stack example | 3005 | `pnpm dev` |
| [federation/epic-stack](./examples/federation/epic-stack) | Module Federation host | 3006 | `pnpm dev` |
| [federation/epic-stack-remote](./examples/federation/epic-stack-remote) | Module Federation remote | 3007 | `pnpm dev` |

Each example has unique ports configured to allow running multiple examples simultaneously.

Expand Down
Loading
Loading