[Frontend] Migrate react-router-dom → react-router v8 (removes the GHSA-qwww-vcr4-c8h2 allowlist entry) #284
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of #282. That issue allowlisted
GHSA-qwww-vcr4-c8h2because there is no fixed version on our line; this is the work that makes the allowlist entry deletable.Why this is a migration and not a bump
react-router-dom'slatestdist-tag is7.18.2— the version we're on — and no 8.x was ever published. In v8 the package was folded intoreact-routeritself, which is at 8.3.0. So:npm audit fix --forceproposes a downgrade to 7.11.0, not an upgradeScope
47 files under
webapp/frontend/srcimport fromreact-router-dom. The imported surface is small and entirely mainstream:No loaders, no actions, no data-router APIs, no RSC. That is about as favourable as a router migration gets — most of the work is likely a mechanical import-specifier change plus whatever v8 renamed or removed.
Also note 10 test files call
vi.mock("react-router-dom", …); those mock targets have to move in lockstep or the mocks silently stop applying and the tests pass against the real router.Not urgent, and worth saying why
The advisory does not apply to us: its own wording is "This only affects your application if you are using the unstable RSC APIs", and this frontend is a plain Vite SPA. So this is hygiene and staying on a supported line, not incident response. It should not jump the queue ahead of roadmap work.
The real cost of staying put is that 7.x is now a legacy line — it will stop getting fixes, and the next react-router advisory may well be one that does apply, with the same "no fixed version available" problem and more urgency.
Approach
vi.mocktargets.eslint srcclean apart from the one known pre-existing warning inCampaignDetail.jsx:521.Navigateredirect that loops) are exactly the class of bug a green unit suite can miss.GHSA-qwww-vcr4-c8h2entry fromscripts/npm-audit-allowlist.jsonin the same PR and confirmnode scripts/check_npm_audit.mjsstill exits 0 — with the entry gone, that is now a real check rather than a formality.Acceptance
react-router-domremoved;react-router@^8.3.0in its placeLabels: frontend
Migration landed in
26d63a5, on top of the Node 22 bump in850fe54. CI green on both. Leaving this open — the manual acceptance item is not something I can verify.A prerequisite this issue didn't anticipate
react-router@8.3.0declaresengines.node ">=22.22.0"and is ESM-only. Every Node pin in the repo was on 20:ci.yml,dependency-audit.yml, and both Dockerfile frontend stages. So this was never a pure import change — it was gated on moving the whole frontend toolchain, including the production image builder.That turned out to matter more than the router. Node 20 reached end-of-life on 2026-04-30 — the production frontend image was being built on an unsupported runtime, independent of react-router.
850fe54moves all four pins together, deliberately: testing on one major while building the image with another is how a runtime-only break ships green.The rest of v8's floor was already satisfied — vite 8.1.5 (needs ≥7) and react/react-dom 19.2.8 (needs ≥19.2.7).
Also fixed en route: the repo had no
.dockerignore, so every build sent ~226 MB ofnode_modulesand the full.githistory to the daemon. On a Windows host it failed outright (invalid file request .../node_modules/.bin/acorn— npm's symlinks don't survive the bind mount), which made building the production image locally impossible. CI never saw it, because a fresh checkout has nonode_modules.Scope was smaller than estimated, once verified
I checked the export surface against the installed package rather than inferring it from the upgrade guide. All nine APIs in use —
BrowserRouter,Link,MemoryRouter,Navigate,Route,Routes,useNavigate,useParams,useSearchParams— are exported fromreact-routerunchanged. OnlyRouterProviderandHydratedRoutermoved toreact-router/dom, and neither is used here.So it reduced to one uniform specifier change: 68 occurrences across 48 files, including all 10
vi.mocktargets. No API renames, no shims.The lockfile lost ~2,650 net lines —
react-router-dompulled a considerably larger tree thanreact-routeralone.Acceptance
react-router-domremoved;react-router@^8.3.0in its placeCampaignDetail.jsx:521warning, #21)On the last one: with
GHSA-qwww-vcr4-c8h2gone the allowlist is[], and bothcheck_npm_audit.mjsinvocations now report "No advisories" rather than "1 allowlisted". The gate is a real check again.Remaining work
A manual pass, ideally with a browser refresh on a deep link (that is what separates a working router from one that only survives client-side navigation):
/dashboard/campaigns/:id— deepest nesting/sessions/:id/campaigns/:id/wikiand a/wiki/:entryIdarticle/adminAuthGuard'sNavigateredirect, the likeliest spot for a redirect loopA green unit suite cannot catch a
Navigatethat loops or a nested route that silently stops matching, which is exactly the class this migration could break. Close once that pass is clean.Manual route pass completed on dev by @rbrooks — all routes working. Closing.
Final state:
26d63a5(migration) on top of850fe54(Node 22 floor), both green in CI and verified on the dev stack.Deploy verification beyond container health:
/dashboard/campaigns/:uuid/sessions/:id/admin/profilereact-router-domreact-router-domremoved;react-router@^8.3.0in its placeGHSA-qwww-vcr4-c8h2is gone andscripts/npm-audit-allowlist.jsonnow has an emptyallowlist, so the audit gate is a real check rather than a formality — the next advisory that lands will actually block.Worth recording for whoever picks up the next router bump: the migration itself was trivial once verified (all nine routing APIs export unchanged from
react-router; onlyRouterProvider/HydratedRoutermoved to thereact-router/domsubpath). The substantive work was the Node 20 → 22 prerequisite, which turned out to matter on its own — Node 20 had been EOL since 2026-04-30 and the production frontend image was being built on it.