remix-run/react-router · error

Path ' ' requires param ' ' but it was not provided

Error message

Path '${path}' requires param '${param}' but it was not provided

What it means

href() interpolates dynamic segments at runtime; any required :param (one without a trailing ?) that has no value in the params argument throws (lib/href.ts:58). With generated route types TypeScript catches this at compile time, so a runtime hit usually means the path string bypassed the typed surface (template literals, casts, or missing typegen).

Solutions

  1. Supply a value for every required :param in the path
  2. Run `react-router typegen` and keep path strings typed so the compiler catches missing params
  3. If the segment is genuinely optional, declare it as :param? in the route path
  4. Check for typos between the param name in the path string and the keys of the params object

Example fix

// before
href('/blog/:slug', {})
// after
href('/blog/:slug', { slug: post.slug })
Defensive patterns

Strategy: validation

Validate before calling

function assertParams(path: string, params: Record<string, string | undefined>) {
  const required = [...path.matchAll(/\/:([\w-]+)(?!\?)/g)].map((m) => m[1]);
  const missing = required.filter((p) => params[p] == null);
  if (missing.length) throw new Error(`Missing params: ${missing.join(', ')}`);
}
assertParams('/users/:id/posts/:postId', params);

Type guard

function hasRequiredParams(path: string, params: unknown): params is Record<string, string> {
  const required = [...path.matchAll(/\/:([\w-]+)(?!\?)/g)].map((m) => m[1]);
  return required.every((p) => typeof (params as any)?.[p] === 'string' && (params as any)[p] !== '');
}

Try / catch

try { return href(path, params) } catch (e) { if (e instanceof Error && e.message.includes('requires param')) { return fallbackPath } throw e }

Prevention

When it happens

Trigger: href('/users/:id', {}) with no id; href(path as any, params) casting away the type; param name typo like href('/blog/:slug', { id: post.id }); passing undefined/null as the params object for a path with required segments.

Common situations: Building path strings dynamically at runtime; skipping `react-router typegen` after adding routes; renaming a route param in routes.ts but not at call sites; copy-pasting href() calls from untyped examples.

Related errors


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

Appendix: source

Thrown at packages/react-router/lib/href.ts:58

 * @category Utils
 * @mode framework
 * @param path The route path to resolve
 * @param args The route params to use when resolving the path
 * @returns The resolved URL path
 */
export function href<Path extends keyof Args>(
  path: Path,
  ...args: Args[Path]
): string {
  let params = args[0];
  let result = trimTrailingSplat(path) // Ignore trailing / and /*, we'll handle it below
    .replace(
      /\/:([\w-]+)(\?)?/g, // same regex as in .\router\utils.ts: compilePath().
      (_: string, param: string, questionMark: string | undefined) => {
        const isRequired = questionMark === undefined;
        const value = params?.[param];
        if (isRequired && value === undefined) {
          throw new Error(
            `Path '${path}' requires param '${param}' but it was not provided`,
          );
        }
        return value == null ? "" : "/" + encodePathParam(stringify(value));
      },
    );

  if (path.endsWith("*")) {
    // treat trailing splat the same way as compilePath, and force it to be as if it were `/*`.
    // `react-router typegen` will not generate the params for a malformed splat,
    // causing a type error, but we can still do the correct thing here.
    const value = params?.["*"];
    if (value !== undefined) {
      result +=
        "/" + stringify(value).split("/").map(encodePathParam).join("/");
    }
  }

View on GitHub (pinned to 6beaca3952)