{"record":{"id":"4860e26ac7d58958","repo":"remix-run/react-router","slug":"the-serverbundles-function-must-only-return-stri","errorCode":null,"errorMessage":"The \"serverBundles\" function must only return strings containing alphanumeric characters and underscores.","messagePattern":"The \"serverBundles\" function must only return strings containing alphanumeric characters and underscores\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/react-router-dev/vite/plugin.ts","lineNumber":3469,"sourceCode":"\n  await Promise.all(\n    getAddressableRoutes(routes).map(async (route) => {\n      let branch = getRouteBranch(routes, route.id);\n      let serverBundleId = await serverBundles({\n        branch: branch.map((route) =>\n          configRouteToBranchRoute({\n            ...route,\n            // Ensure absolute paths are passed to the serverBundles function\n            file: path.join(resolvedAppDirectory, route.file),\n          }),\n        ),\n      });\n      if (typeof serverBundleId !== \"string\") {\n        throw new Error(`The \"serverBundles\" function must return a string`);\n      }\n      // Server bundle IDs must be valid Vite environment names, so hyphens are not allowed\n      if (!/^[a-zA-Z0-9_]+$/.test(serverBundleId)) {\n        throw new Error(\n          `The \"serverBundles\" function must only return strings containing alphanumeric characters and underscores.`,\n        );\n      }\n      buildManifest.routeIdToServerBundleId[route.id] = serverBundleId;\n\n      buildManifest.serverBundles[serverBundleId] ??= {\n        id: serverBundleId,\n        file: normalizePath(\n          path.join(\n            path.relative(\n              rootDirectory,\n              path.join(serverBuildDirectory, serverBundleId),\n            ),\n            reactRouterConfig.serverBuildFile,\n          ),\n        ),\n      };\n    }),","sourceCodeStart":3451,"sourceCodeEnd":3487,"githubUrl":"https://github.com/remix-run/react-router/blob/6beaca39526d5716c3c112ebb0782765baa5a9ce/packages/react-router-dev/vite/plugin.ts#L3451-L3487","documentation":"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.","triggerScenarios":"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.","commonSituations":"Using human-friendly kebab-case bundle names; building IDs from `route.file` substrings that contain `.` (route files like `route._index.tsx`) or `/`.","solutions":["Use only letters, digits, and underscores: rename `'marketing-site'` to `'marketing_site'`","Sanitize derived IDs: `id.replace(/[^a-zA-Z0-9_]/g, '_')`","Keep a fixed allowlist of bundle IDs in config rather than generating them from file paths"],"exampleFix":"// before\nserverBundles: ({ branch }) =>\n  branch.includes(isAdmin) ? 'admin-bundle' : 'main',\n\n// after\nserverBundles: ({ branch }) =>\n  branch.includes(isAdmin) ? 'admin_bundle' : 'main',","handlingStrategy":"validation","validationCode":"// Centralize and sanitize bundle IDs\nconst toBundleId = (raw: string): string => raw.replace(/[^a-zA-Z0-9_]/g, '_');\nconst serverBundles = async ({ branch }) => {\n  return toBundleId(branch.at(-1)!.id ?? 'index');\n};","typeGuard":"const isValidBundleId = (v: string): boolean => /^[a-zA-Z0-9_]+$/.test(v);","tryCatchPattern":"try {\n  await build();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('alphanumeric characters and underscores')) {\n    // replace '-' and '.' in returned bundle IDs with '_'\n  }\n}","preventionTips":["Use snake_case bundle IDs exclusively","Sanitize any ID derived from route file paths (replace `/`, `.`, `-`)","Keep a const allowlist of bundle names in config"],"tags":["server-bundles","config","build","validation"],"backgroundTag":"config-callback-invalid-return","analyzedSha":"6beaca39526d5716c3c112ebb0782765baa5a9ce","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}