{"record":{"id":"1255f066a5c50266","repo":"remix-run/react-router","slug":"custom-vite-manifest-paths-are-not-supported","errorCode":null,"errorMessage":"Custom Vite manifest paths are not supported","messagePattern":"Custom Vite manifest paths are not supported","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router-dev/vite/plugin.ts","lineNumber":126,"sourceCode":"  root,\n  plugins,\n}: {\n  configFile?: string;\n  mode?: string;\n  plugins?: Vite.Plugin[];\n  root: string;\n}) {\n  let vite = getVite();\n\n  let viteConfig = await vite.resolveConfig(\n    { mode, configFile, root, plugins },\n    \"build\", // command\n    \"production\", // default mode\n    \"production\", // default NODE_ENV\n  );\n\n  if (typeof viteConfig.build.manifest === \"string\") {\n    throw new Error(\"Custom Vite manifest paths are not supported\");\n  }\n\n  return viteConfig;\n}\n\nexport function extractPluginContext(\n  viteConfig: Vite.ResolvedConfig | Vite.UserConfig,\n) {\n  return viteConfig[\"__reactRouterPluginContext\" as keyof typeof viteConfig] as\n    | ReactRouterPluginContext\n    | undefined;\n}\n\nconst SERVER_ONLY_ROUTE_EXPORTS = [\"loader\", \"action\", \"middleware\", \"headers\"];\nconst CLIENT_NON_COMPONENT_EXPORTS = [\n  \"clientAction\",\n  \"clientLoader\",\n  \"clientMiddleware\",","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router-dev/vite/plugin.ts#L108-L144","documentation":"Thrown in `resolveViteConfig` after Vite resolves the user config. React Router writes its build manifest to a fixed, well-known path inside the build directory and reads it back during SSR/preview, so a user-supplied `build.manifest` string (Vite's option for naming `manifest.json`) would relocate Vite's own manifest and break React Router's lookups. Only `build.manifest === true` (or unset) is compatible.","triggerScenarios":"User sets `build.manifest: \"custom-name.json\"` (a string) in `vite.config.ts`; or copies a Vue/legacy config that used a custom manifest filename; or sets `build.manifest` conditionally to a string in production.","commonSituations":"Migrating from a hand-rolled Vite SPA setup that pinned `manifest.json` to a specific name; copy-pasting config from a non-React-Router tutorial; enabling Vite's manifest feature with a custom name thinking React Router respects it.","solutions":["Remove the custom string from `build.manifest` in `vite.config.ts`; leave it unset or set it to `true`.","If you need a specific manifest name for a downstream tool, post-process the built file with a script after `react-router build` instead of using Vite's option.","Search the repo for `manifest:` to catch a stale override."],"exampleFix":"// before\nexport default defineConfig({\n  plugins: [reactRouter()],\n  build: { manifest: \"my-manifest.json\" },\n});\n// after\nexport default defineConfig({\n  plugins: [reactRouter()],\n  build: { manifest: true },\n});","handlingStrategy":"validation","validationCode":"import type { UserConfig } from \"vite\";\nconst assertNoCustomManifest = (cfg: UserConfig) => {\n  if (typeof cfg.build?.manifest === \"string\") {\n    throw new Error(\"build.manifest must be boolean, not a custom string\");\n  }\n};","typeGuard":"const isManifestSafe = (m: unknown): boolean =>\n  m === undefined || m === true || m === false;","tryCatchPattern":null,"preventionTips":["Never set `build.manifest` to a string in React Router projects.","Audit `vite.config.ts` after copying config from non-React-Router templates.","Prefer leaving `build.manifest` unset — React Router manages it."],"tags":["vite","config","manifest","build"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}