remix-run/react-router · error · Error
The "serverBundles" function must only return strings contai
Error message
The "serverBundles" function must only return strings containing alphanumeric characters and underscores.
What it means
Thrown immediately after the string-typed check on the same value. Server bundle IDs become Vite environment names, which disallow hyphens and other punctuation, so React Router enforces `^[a-zA-Z0-9_]+$`. A return value like `"admin-bundle"`, `"v1.2"`, or one containing slashes/spaces fails the regex and aborts the build.
Source
Thrown at packages/react-router-dev/vite/plugin.ts:3469
await Promise.all(
getAddressableRoutes(routes).map(async (route) => {
let branch = getRouteBranch(routes, route.id);
let serverBundleId = await serverBundles({
branch: branch.map((route) =>
configRouteToBranchRoute({
...route,
// Ensure absolute paths are passed to the serverBundles function
file: path.join(resolvedAppDirectory, route.file),
}),
),
});
if (typeof serverBundleId !== "string") {
throw new Error(`The "serverBundles" function must return a string`);
}
// Server bundle IDs must be valid Vite environment names, so hyphens are not allowed
if (!/^[a-zA-Z0-9_]+$/.test(serverBundleId)) {
throw new Error(
`The "serverBundles" function must only return strings containing alphanumeric characters and underscores.`,
);
}
buildManifest.routeIdToServerBundleId[route.id] = serverBundleId;
buildManifest.serverBundles[serverBundleId] ??= {
id: serverBundleId,
file: normalizePath(
path.join(
path.relative(
rootDirectory,
path.join(serverBuildDirectory, serverBundleId),
),
reactRouterConfig.serverBuildFile,
),
),
};
}),View on GitHub (pinned to 1fd704a7da)
Solutions
- Use only `[A-Za-z0-9_]` in the returned ID — prefer underscores over hyphens (e.g. `admin_area`).
- If deriving from paths, sanitize: `id.replace(/[^a-zA-Z0-9_]/g, "_")`.
- Pin the function's return type to a branded `ServerBundleId` type to catch slips.
Example fix
// before
export const serverBundles = ({ branch }) => "admin-bundle";
// after
export const serverBundles = ({ branch }) => "admin_bundle"; Defensive patterns
Strategy: validation
Validate before calling
const VALID = /^[a-zA-Z0-9_]+$/;
const assertValidBundleId = (id: string) => {
if (!VALID.test(id)) throw new Error(`Invalid bundle id: ${id}`);
}; Type guard
const isValidBundleId = (v: unknown): v is string => typeof v === "string" && /^[a-zA-Z0-9_]+$/.test(v);
Prevention
- Restrict bundle IDs to `[A-Za-z0-9_]` — use underscores, not hyphens.
- If deriving IDs from paths, sanitize with `replace(/[^a-zA-Z0-9_]/g, "_")`.
- Brand the return type so the compiler enforces the format.
When it happens
Trigger: Returning a hyphenated, dotted, spaced, or otherwise punctuated bundle ID from `serverBundles`; constructing IDs dynamically from route file paths (which contain `/` and `.`); copy-pasting IDs from a naming convention that uses dashes.
Common situations: Naming bundles by feature with dashes (`admin-area`); using kebab-case from route file paths; internationalized IDs with non-ASCII characters.
Related errors
- The "serverBundles" function must return a string
- React Router Vite plugin not found in Vite config
- Custom Vite manifest paths are not supported
- Unable to prerender path because it does not match any route
- ${configResult.error}
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/4860e26ac7d58958.
Report an issue: GitHub.