Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
1a7f65f
chore: simplify route restart marker read
ScriptedAlchemy Jun 20, 2026
c673adc
chore: update yuku printer
ScriptedAlchemy Jun 20, 2026
d93adf4
Merge remote-tracking branch 'origin/main' into perf/bundling-perform…
ScriptedAlchemy Jun 21, 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.
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
7 changes: 4 additions & 3 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ counts for stress testing.

All benchmark profiles generate deterministic synthetic React Router apps under
`.benchmark/fixtures/`, build the current plugin package once, then run Rsbuild
production builds against those fixtures.
builds with `pluginReactRouter({ logPerformance: true })`.

To capture Rspack tracing output for a benchmark, pass `--rspack-profile`:

Expand Down Expand Up @@ -58,8 +58,9 @@ Each run writes:
- `.benchmark/results/<profile>/baseline.md`

The JSON includes wall time, optional GNU `/usr/bin/time -v` user/sys/RSS data,
per-run exit status, and Rspack trace artifact paths when tracing is enabled.
The markdown report summarizes the same benchmark-level timing and memory data
parsed `[react-router:performance]` reports from the plugin, and an aggregated
`pluginOperations` table per fixture. The markdown report includes the same
operation breakdown so route transforms and manifest work can be compared
without opening the raw JSON.

## Hygiene
Expand Down
Loading
Loading