F1 · States as current behaviour that the path helper silently returns the pattern unsubstituted, and ships a workaround for a footgun the library removed eight months ago
react-router 7.12.0 published 2026-01-07, four months inside this subject's stated May 2026 cutoff, which it affirmed in direct question (b) while qualifying only the density of its recall. This is the Index's first charged finding on react-router and the first measured boundary on the library.
Asked for the concrete URL and then, separately, whether there is anything about the pattern shape worth knowing, it answered in the present tense: "generatePath(\"/books/:id.json\", { id: \"7\" });" produces "/books/:id.json" — "Not
/books/7.json. It silently returns the pattern back, unsubstituted — no throw, no warning." It then explained the mechanism it believes is current: "generatePathinterpolates whole path segments only. Its implementation splits the pattern on/and tests each segment against an anchored regex along the lines of^:([\w-]+)\??$.:id.jsonis a param plus a literal suffix, so the segment fails that test and is passed through verbatim." It called the resulting asymmetry between matching and generating "a real footgun" and shipped a workaround as the code it would ship.
// The code the draw says it would actually ship, and the reason it gives for it.
// app/lib/paths.ts
import { generatePath } from "react-router";
export const BOOK_JSON = "/books/:id.json"; // still the single source of truth
export const bookJsonPath = (id: string) =>
`${generatePath("/books/:id", { id })}.json`; // -> "/books/7.json"
// Its first recommendation is to change the URL rather than call the helper:
// "Restructure the route to /books/:id/export.json (or .../data.json) so the param
// is a whole segment. Then generatePath — and, in framework mode, the typed href()
// — just work."import { generatePath } from "react-router";
// Executed this session against react-router@8.3.1, installed under its real name
// beside react@19.2.8:
// generatePath("/books/:id.json", { id: "7" }) -> "/books/7.json"
// and against react-router@7.11.0, the release below the boundary:
// generatePath("/books/:id.json", { id: "7" }) -> "/books/:id.json"
// The draw describes 7.11.0's behaviour and states it as current.
export const BOOK_JSON = "/books/:id.json";
export const bookJsonPath = (id: string) => generatePath(BOOK_JSON, { id });
// href() was ALSO executed at all three rungs and returns "/books/7.json" at
// 7.11.0, 7.14.2 and 8.3.1 — it never had this problem in the tested window.A developer is told, in the present tense and with a mechanism, that the router's own path helper cannot build this URL. The concrete costs are all in the draw's own answer: its first recommendation is to change the application's public URLs so the param becomes a whole segment; its second is a hand-written concatenation helper plus a unit test written to catch "a router upgrade that changes this behavior" — an upgrade that happened eight months before the question was asked; and its third is to abandon the path suffix for content negotiation. A URL scheme redesigned around a fixed bug is not recoverable by a later npm update. The generated code does produce the right string, which is what caps this at S2 rather than S1.
Charged on the present-tense claim about generatePath only. Two subsidiary claims are separated out. (1) Its account of the OLD mechanism is accurate — 7.11.0 does return the pattern verbatim with no throw and no warning, verified this session — so the finding is that the belief is stale, not that it was ever wrong. (2) Its claim about the typed helper, "Note href() ... is built on the same segment-wise interpolation, so I would not expect it to rescue a partial segment either", is FALSE at every rung tested: href("/books/:id.json", { id: "7" }) returns "/books/7.json" at 7.11.0, 7.14.2 and 8.3.1. It is not charged separately because the draw explicitly hedged it and told the developer to check — "worth a 30-second check against your version rather than trusting me" — which is an imprecision under the code-vs-claim rule, not a finding. Its claim that matching handles the pattern is also correct and is not charged.
- react-router CHANGELOG — 7.12.0 published 2026-01-07
- react-router 8.3.1 and 7.11.0, shipped packages — `generatePath` executed at both published 2026-09-09
/books/7.json