The newest breaking major on the board, and Claude Opus 5 already writes most of it. It drops json() and defer() unprompted, reaches for data() when it needs a status, and reads middleware context the v8 way. One task fails, every time: the meta function still receives the data argument that v8 removed.
93/100
Agent-readiness
14 compile clean1 fails type-check
Model: claude-opus-5Tasks: 15Method: tsc against real installed packageRun: 2026-07-31
v8 removed the data parameter from the meta APIs in favour of loaderData, to line up with Route.ComponentProps. The model writes the old one — and it isn't a fluke: re-run three more times, it failed 3 of 3.
✗Set the page title from the loader's dataTS2339 · 3 of 3 runs
error TS2339: Property 'data' does not exist on type 'MetaArgs<() => Promise<{ user: … }>>'.
Why it slips through review: the rename touched MetaArgs, MetaMatch, Route.ComponentProps.matches and UIMatch together, and the old name is the more natural word. A reader skims data in a meta function and sees nothing wrong. The compiler doesn't.
Clean
What it gets right — 14 of 15
The removals it could have tripped on, it didn't.
v8 deleted a lot: json(), defer(), the whole react-router-dom package, and the plain-object context. The model wrote none of them. It returns bare objects from loaders, hands back an un-awaited promise for streaming instead of defer(), wraps values in data() when it needs a 400, calls createContext() without the old unstable_ prefix, and reads middleware state with context.get(userContext) rather than as a property.
loader / actionredirect()useLoaderDatauseFetcheruseSubmituseSearchParamsisRouteErrorResponsecreateBrowserRouterno json() — returns plain datano defer() — raw promisedata() for status codescreateContext — no unstable_context.get() — not a plain object
Six scores, one pattern
Readiness tracks the drift window
93 on a major this new is the surprise here. The pattern across this board has been that the most recent breaking change scores worst — Prisma 7 sits at 87 for exactly that reason. React Router 8 is newer still and lands at 93, with a single miss.
The plausible reason is that v8 mostly finished removals that v7 had already started deprecating: json() and defer() were on the way out long before they were deleted, so the model had years of "don't use this" signal to learn from. The one thing it gets wrong is the one thing that was a genuine rename rather than a long-signposted removal.
Which is the more useful version of the drift rule: it isn't the age of the release that predicts the gap, it's how much warning the ecosystem had.
No opinions — just the compiler
How this was measured
15 realistic React Router tasks → generated by claude-opus-5 → each written into a project with the real installed react-router v8.3.0 and type-checked with tsc --noEmit.
A task passes only if it compiles clean. Prompts name the building blocks (loader, action, middleware, useFetcher, …) but never the signatures — so this measures what the model reaches for. Failures are the compiler's own diagnostics. Reproducible: pinned versions, pinned model.
One thing this score cannot see. v8 deleted the react-router-dom package and asks you to import RouterProvider from react-router/dom. The model imported it from react-router — which still type-checks, because the root entry re-exports it. So that task passed, and the drift it was written to catch is invisible to a type-checker.
Same limit applies board-wide: tsc catches "this does not exist" and "wrong shape". It cannot catch code that compiles and means something else.
Is your SDK ready for AI agents?
Right after a major release, agents keep shipping your old API. SDKProof scores it, tracks it across model releases, and tells you exactly what to fix.