remix-run/react-router · error

The `prerender.concurrency` config must be a positive intege

Error message

The `prerender.concurrency` config must be a positive integer if specified.

What it means

When `prerender` is an object with a `concurrency` key, the value must be a positive integer (`Number.isInteger(n) && n > 0`). Any other value — 0, negative numbers, floats like 2.5, `Infinity`, `NaN`, or strings — fails `isValidConcurrencyConfig` and returns this error, because the value is used to size the prerender worker/task pool.

Source

Thrown at packages/react-router-dev/config/config.ts:579

          "of string paths, or a function returning a boolean or array of string paths.",
      );
    }

    if (typeof prerender === "object" && "unstable_concurrency" in prerender) {
      return err(
        "The `prerender.unstable_concurrency` config field has been stabilized as `prerender.concurrency`",
      );
    }

    let isValidConcurrencyConfig =
      typeof prerender != "object" ||
      !("concurrency" in prerender) ||
      (typeof prerender.concurrency === "number" &&
        Number.isInteger(prerender.concurrency) &&
        prerender.concurrency > 0);

    if (!isValidConcurrencyConfig) {
      return err(
        "The `prerender.concurrency` config must be a positive integer if specified.",
      );
    }
  }

  let routeDiscovery: ResolvedReactRouterConfig["routeDiscovery"];
  if (userRouteDiscovery == null) {
    if (ssr) {
      routeDiscovery = {
        mode: "lazy",
        manifestPath: "/__manifest",
      };
    } else {
      routeDiscovery = { mode: "initial" };
    }
  } else if (userRouteDiscovery.mode === "initial") {
    routeDiscovery = userRouteDiscovery;
  } else if (userRouteDiscovery.mode === "lazy") {

View on GitHub (pinned to 6beaca3952)

Solutions

  1. Set an explicit positive integer, e.g. `concurrency: 4`.
  2. Coerce computed/env values: `concurrency: Number.parseInt(String(process.env.PRERENDER_CONCURRENCY ?? "4"), 10)` and clamp to >= 1.
  3. Remove the `concurrency` key to accept the default.

Example fix

// before
export default {
  prerender: { concurrency: Number(process.env.PRERENDER_CONCURRENCY ?? "4.5") },
};

// after
export default {
  prerender: {
    concurrency: Math.max(1, Number.parseInt(String(process.env.PRERENDER_CONCURRENCY ?? "4"), 10)),
  },
};
Defensive patterns

Strategy: validation

Validate before calling

function toConcurrency(raw: unknown): number | undefined {
  const n = typeof raw === "number" ? raw : Number.parseInt(String(raw ?? ""), 10);
  return Number.isInteger(n) && n > 0 ? n : undefined;
}
// config: prerender: { concurrency: toConcurrency(process.env.PRERENDER_CONCURRENCY) ?? 4 }

Type guard

function isPositiveInteger(v: unknown): v is number {
  return typeof v === "number" && Number.isInteger(v) && v > 0;
}

Prevention

When it happens

Trigger: Setting `prerender: { concurrency: 0 }` (perhaps intending 'unlimited'), a fractional value from a computed config (`Math.log2(cpus)`), or a value parsed from an env string (`process.env.CONCURRENCY` yields `"4"`, not `4`).

Common situations: Deriving concurrency from `navigator.hardwareConcurrency`-style math that yields non-integers; reading it from env vars without `parseInt`; copying a config that assumed 0 meant unlimited.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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