feat: migrate to React Router v8 #79
No reviewers
Labels
No labels
dependencies
major
security-pin
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
trails-cool/trails!79
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/react-router-v8"
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?
Replaces #67, #68, #69 and #71. Closes them in favour of one PR, because none of them could ever pass individually.
Why four PRs were all red
react-routerships as one release train — the runtime and thedev/node/serveadapters are published in lockstep and are not compatible across a major. Renovate had them ungrouped, so it opened four v8 PRs, each bumping one catalog entry while the others stayed on^7.18.2:react-router ^8.0.0^7.18.2@react-router/dev@react-router/node@react-router/serveEvery one was a v8 runtime against v7 tooling. #71's failure —
TS2305: Module 'react-router' has no exported member 'AppLoadContext'— reads like a migration bug but was really a split version set. Same failure shape as the Playwright image in #62: a set that must move together, split across PRs that are each green alone.All four catalog entries now move to
^8.3.0in one commit, andrenovate.json5groups them so this cannot recur.What v8 actually required
Most of v8 does not apply here: no
react-router-domimports anywhere, Vite is already 8.2.1 (v8 needs ≥7), no Cloudflare dev proxy, no architect adapter, nosplitRouteModulesusage.AppLoadContextremoved. The entry-server hook is nowloadContext: RouterContextProvider, verified againstreact-router@8.3.0's actualHandleDocumentRequestFunction:Both apps'
entry.server.tsxare byte-identical and name the parameter_loadContext— deliberately unused — so this is a type annotation swap.The
dataarg in meta APIs removed. v8'sMetaArgsexposesloaderData,params,location,matches. Seven route modules used it. A grep formeta.*datafound only two; the other five destructure and rename ({ data: loaderData }), sopnpm typecheckis what found them — worth noting as the reason typecheck is the real gate on this PR rather than a formality.future.v8_middlewareno longer exists, middleware being the default. The journal had opted in for ActivityPub content negotiation on/users/:usernameand/activities/:id; undersatisfies Config, keeping a removed key is a type error. Both middleware functions destructure only{ request }, and nothing in either app reads a loader/actioncontext— so theRouterContextProvidertype change is the no-op the original config comment claimed. Typegen confirms it:+future.tsis no longer emitted andMiddlewareFunctionis generated unconditionally.engines.nodewas">=20"; v8 requires>=22.22.0. Nothing actually ran on 20 — CI uses node 24, both runtime images arenode:26-slim— so this only stops the declared floor understating the requirement.The
reactcatalog floor also moves^19.2.5→^19.2.7, v8's minimum. It already resolved to 19.2.7, so nothing changes except that the constraint stops lying.The Renovate rule placement is deliberate
It sits after the
majors -> groupName: nullrule, unliketesting toolingabove it. Later rules win, so a rule placed before that one loses its groupName on a major. That is intentional for test tooling — a major should be reviewed on its own — and exactly wrong here: a react-router major is the case that most needs the four to arrive together. Had this rule gone before it, v8 would have re-split into four PRs and reproduced the bug the rule exists to prevent.addLabels: ["major"]still applies, since rules merge field-wise.Testing
Verified locally on node 24.16.0 (a stock local asdf may select 22.15.0, which is now below the declared floor):
pnpm typecheck14/14 — the gate that found five of the seven meta call sitespnpm lint14/14pnpm test12/12pnpm build2/2react-routerand all three adapters at 8.3.0; the lockfile holds no v7 copyRoute.MiddlewareFunction[]E2E is left to CI, which boots the app through
react-router serve— the path most likely to surface an ESM-only regression, and the one thing I cannot exercise locally without Postgres and BRouter.