remix-run/react-router · error · Error

Custom Vite manifest paths are not supported

Error message

Custom Vite manifest paths are not supported

What it means

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.

Source

Thrown at packages/react-router-dev/vite/plugin.ts:126

  root,
  plugins,
}: {
  configFile?: string;
  mode?: string;
  plugins?: Vite.Plugin[];
  root: string;
}) {
  let vite = getVite();

  let viteConfig = await vite.resolveConfig(
    { mode, configFile, root, plugins },
    "build", // command
    "production", // default mode
    "production", // default NODE_ENV
  );

  if (typeof viteConfig.build.manifest === "string") {
    throw new Error("Custom Vite manifest paths are not supported");
  }

  return viteConfig;
}

export function extractPluginContext(
  viteConfig: Vite.ResolvedConfig | Vite.UserConfig,
) {
  return viteConfig["__reactRouterPluginContext" as keyof typeof viteConfig] as
    | ReactRouterPluginContext
    | undefined;
}

const SERVER_ONLY_ROUTE_EXPORTS = ["loader", "action", "middleware", "headers"];
const CLIENT_NON_COMPONENT_EXPORTS = [
  "clientAction",
  "clientLoader",
  "clientMiddleware",

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Remove the custom string from `build.manifest` in `vite.config.ts`; leave it unset or set it to `true`.
  2. 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.
  3. Search the repo for `manifest:` to catch a stale override.

Example fix

// before
export default defineConfig({
  plugins: [reactRouter()],
  build: { manifest: "my-manifest.json" },
});
// after
export default defineConfig({
  plugins: [reactRouter()],
  build: { manifest: true },
});
Defensive patterns

Strategy: validation

Validate before calling

import type { UserConfig } from "vite";
const assertNoCustomManifest = (cfg: UserConfig) => {
  if (typeof cfg.build?.manifest === "string") {
    throw new Error("build.manifest must be boolean, not a custom string");
  }
};

Type guard

const isManifestSafe = (m: unknown): boolean =>
  m === undefined || m === true || m === false;

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/1255f066a5c50266. Report an issue: GitHub.