Documentation Index
Fetch the complete documentation index at: https://mintlify.com/remix-run/react-router/llms.txt
Use this file to discover all available pages before exploring further.
react-router.config.ts
Configures React Router application settings. Located at react-router.config.ts in your project root.
Import
import type { Config } from "@react-router/dev/config";
ReactRouterConfig Type
export interface ReactRouterConfig {
appDirectory?: string;
basename?: string;
buildDirectory?: string;
buildEnd?: BuildEndHook;
future?: Partial<FutureConfig>;
prerender?: PrerenderConfig;
presets?: Preset[];
routeDiscovery?: RouteDiscoveryConfig;
serverBuildFile?: string;
serverBundles?: ServerBundlesFunction;
serverModuleFormat?: "esm" | "cjs";
ssr?: boolean;
allowedActionOrigins?: string[];
}
Configuration Options
appDirectory
Path to the application directory, relative to project root.export default {
appDirectory: "src",
};
basename
Base path for all routes. Useful when your app is served from a subdirectory.export default {
basename: "/my-app",
};
Must match Vite’s base config:export default defineConfig({
base: "/my-app",
plugins: [reactRouter()],
});
buildDirectory
Path to the build output directory, relative to project root.export default {
buildDirectory: "dist",
};
Output structure:dist/
├── client/ # Client assets
└── server/ # Server bundle(s)
buildEnd
Hook called after build completes. Useful for custom post-build tasks.type BuildEndHook = (args: {
buildManifest: BuildManifest | undefined;
reactRouterConfig: ResolvedReactRouterConfig;
viteConfig: ResolvedConfig;
}) => void | Promise<void>;
Example:export default {
async buildEnd({ buildManifest, viteConfig }) {
// Generate custom manifest
await writeFile(
"build/custom-manifest.json",
JSON.stringify(buildManifest)
);
// Copy additional files
await cp("public", "build/client/public", { recursive: true });
},
};
future
Enables future features and breaking changes.interface FutureConfig {
unstable_optimizeDeps: boolean;
unstable_subResourceIntegrity: boolean;
unstable_trailingSlashAwareDataRequests: boolean;
unstable_previewServerPrerendering: boolean;
v8_middleware: boolean;
v8_splitRouteModules: boolean | "enforce";
v8_viteEnvironmentApi: boolean;
}
Example:export default {
future: {
v8_middleware: true,
v8_splitRouteModules: "enforce",
},
};
Flags:
unstable_optimizeDeps - Optimize dependency pre-bundling
unstable_subResourceIntegrity - Generate SRI hashes for scripts
unstable_trailingSlashAwareDataRequests - Handle trailing slashes in data requests
unstable_previewServerPrerendering - Prerender with Vite preview server
v8_middleware - Enable route middleware
v8_splitRouteModules - Automatic route code splitting (true | "enforce")
v8_viteEnvironmentApi - Use Vite Environment API
prerender
prerender
boolean | string[] | function | object
Defines which routes to prerender at build time as static HTML.Types:type PrerenderConfig =
| boolean // true = all routes, false = none
| string[] // Specific paths
| ((args: { getStaticPaths: () => string[] }) => string[] | Promise<string[]>)
| {
paths: boolean | string[] | Function;
unstable_concurrency?: number;
};
Examples:export default {
prerender: true,
};
presets
Configuration presets for platform integrations.interface Preset {
name: string;
reactRouterConfig?: (args: {
reactRouterUserConfig: ReactRouterConfig;
}) => ConfigPreset | Promise<ConfigPreset>;
reactRouterConfigResolved?: (args: {
reactRouterConfig: ResolvedReactRouterConfig;
}) => void | Promise<void>;
}
Example:import { cloudflarePreset } from "@react-router/cloudflare";
export default {
presets: [cloudflarePreset()],
};
routeDiscovery
Controls lazy route discovery behavior.type RouteDiscoveryConfig =
| { mode: "lazy"; manifestPath?: string }
| { mode: "initial" };
Default: { mode: "lazy", manifestPath: "/__manifest" } (when SSR enabled)Examples:export default {
routeDiscovery: {
mode: "lazy",
manifestPath: "/__manifest", // Custom path
},
};
Lazy mode requires SSR. With ssr: false, mode is automatically "initial".
serverBuildFile
Filename for the server build output.export default {
serverBuildFile: "server.js",
};
Output: build/server/server.js
serverBundles
Split server code into multiple bundles based on route.type ServerBundlesFunction = (args: {
branch: BranchRoute[];
}) => string | Promise<string>;
interface BranchRoute {
id: string;
path?: string;
file: string;
index?: boolean;
}
Example:export default {
serverBundles({ branch }) {
// Check if any route in branch starts with "routes/admin"
const isAdminRoute = branch.some(
(route) => route.id.startsWith("routes/admin")
);
const isApiRoute = branch.some(
(route) => route.id.startsWith("routes/api")
);
if (isAdminRoute) return "admin";
if (isApiRoute) return "api";
return "main";
},
};
Outputs:build/server/
├── main/
│ └── index.js
├── admin/
│ └── index.js
└── api/
└── index.js
serverModuleFormat
'esm' | 'cjs'
default:"esm"
Module format for server build output.export default {
serverModuleFormat: "cjs",
};
Most modern runtimes support ESM. Use CJS only if required by your deployment platform.
ssr
Enables server-side rendering. Set to false for SPA mode.export default {
ssr: false, // SPA mode
};
SPA Mode:
- Pre-renders
/ at build time
- Saves as
index.html
- No server required
- All routes client-side only
allowedActionOrigins
Whitelist of allowed origins for form submissions. Supports glob patterns.export default {
allowedActionOrigins: [
"example.com",
"*.example.com", // sub.example.com
"**.example.com", // sub.domain.example.com
],
};
Does not apply to resource routes (routes without UI components).
Runtime Override:import type { ServerBuild } from "react-router";
async function getBuild(): Promise<ServerBuild> {
const build = await import("./build/server/index.js");
return {
...build,
allowedActionOrigins: [
process.env.ALLOWED_ORIGIN,
],
};
}
Complete Example
import type { Config } from "@react-router/dev/config";
export default {
appDirectory: "app",
basename: "/",
buildDirectory: "build",
future: {
v8_middleware: true,
v8_splitRouteModules: "enforce",
},
async prerender({ getStaticPaths }) {
const posts = await fetchPosts();
return [
"/",
"/about",
...posts.map(p => `/blog/${p.slug}`),
...getStaticPaths(),
];
},
routeDiscovery: {
mode: "lazy",
},
serverBundles({ branch }) {
const isAdminRoute = branch.some(
route => route.id.startsWith("routes/admin")
);
return isAdminRoute ? "admin" : "main";
},
serverModuleFormat: "esm",
ssr: true,
allowedActionOrigins: [
"example.com",
"*.example.com",
],
async buildEnd({ buildManifest }) {
console.log("Build completed!");
// Custom post-build logic
},
} satisfies Config;
Common Patterns
Multi-Region Deployment
export default {
serverBundles({ branch }) {
// Split by geographic region based on route prefix
const path = branch[branch.length - 1]?.path || "";
if (path.startsWith("/eu")) return "eu";
if (path.startsWith("/asia")) return "asia";
return "us";
},
};
Development vs Production
const isDev = process.env.NODE_ENV === "development";
export default {
buildDirectory: isDev ? "dev-build" : "build",
future: {
v8_middleware: !isDev, // Only in production
},
prerender: isDev ? false : ["/", "/about"],
};
Incremental Static Regeneration Pattern
export default {
async prerender({ getStaticPaths }) {
// Only prerender popular pages at build time
const popular = await getPopularPages();
return [
"/",
...popular,
// Other pages rendered on-demand
];
},
};
Custom Build Artifacts
import { writeFile } from "fs/promises";
export default {
async buildEnd({ buildManifest, reactRouterConfig }) {
// Generate custom route manifest for analytics
const routes = Object.keys(buildManifest.routes);
await writeFile(
"build/routes.json",
JSON.stringify(routes, null, 2)
);
// Copy additional assets
await cp(
"locales",
`${reactRouterConfig.buildDirectory}/client/locales`,
{ recursive: true }
);
},
};
TypeScript
Ensure proper type checking:
import type { Config } from "@react-router/dev/config";
export default {
appDirectory: "app",
ssr: true,
// ...
} satisfies Config;
Validation
Config is validated at build time:
// ✅ Valid
export default {
appDirectory: "app",
ssr: true,
};
// ❌ Invalid - wrong type
export default {
ssr: "yes", // Error: Expected boolean
};
// ❌ Invalid - incompatible options
export default {
ssr: false,
routeDiscovery: { mode: "lazy" }, // Error: lazy requires SSR
};