remix-run/react-router · error

The "serverBundles" function must only return strings…

Error message

The "serverBundles" function must only return strings containing alphanumeric characters and underscores.

What it means

Beyond being a string, a `serverBundles` return value must match `/^[a-zA-Z0-9_]+$/` because the ID becomes a Vite environment name and a filesystem path segment — hyphens, dots, slashes and unicode are rejected. This guard fires right after the string check, naming the constraint explicitly.

Solutions

  1. Use only letters, digits, and underscores: rename `'marketing-site'` to `'marketing_site'`
  2. Sanitize derived IDs: `id.replace(/[^a-zA-Z0-9_]/g, '_')`
  3. Keep a fixed allowlist of bundle IDs in config rather than generating them from file paths

Example fix

// before
serverBundles: ({ branch }) =>
  branch.includes(isAdmin) ? 'admin-bundle' : 'main',

// after
serverBundles: ({ branch }) =>
  branch.includes(isAdmin) ? 'admin_bundle' : 'main',
Defensive patterns

Strategy: validation

Validate before calling

// Centralize and sanitize bundle IDs
const toBundleId = (raw: string): string => raw.replace(/[^a-zA-Z0-9_]/g, '_');
const serverBundles = async ({ branch }) => {
  return toBundleId(branch.at(-1)!.id ?? 'index');
};

Type guard

const isValidBundleId = (v: string): boolean => /^[a-zA-Z0-9_]+$/.test(v);

Try / catch

try {
  await build();
} catch (e) {
  if (e instanceof Error && e.message.includes('alphanumeric characters and underscores')) {
    // replace '-' and '.' in returned bundle IDs with '_'
  }
}

Prevention

When it happens

Trigger: Returning `'marketing-site'`, `'bundle.1'`, or `'fr/français'` from the `serverBundles` function; deriving IDs from route file paths without sanitizing (slashes/dots leak in); locale-based IDs with dashes.

Common situations: Using human-friendly kebab-case bundle names; building IDs from `route.file` substrings that contain `.` (route files like `route._index.tsx`) or `/`.

Related errors


AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18). Data as JSON: /api/errors/4860e26ac7d58958. Report an issue: GitHub.

Appendix: 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 6beaca3952)