Upgrade React Router to v8.3.0 and bump Node minimum to 22.22.0
Type of change
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
- This change requires a documentation update
Changes To Core Features
Summary
Upgrades the frontend toolchain to React Router 8.3.0 (from 7.18.2), which requires Node 22.22.0+ as a baseline. This is a breaking change for the framework's minimum Node version requirement.
Three independent commits, reviewable in order:
Upgrade react-router to 8.3.0— the upgrade itselfCorrect the vite version claim in the reflex-base 0.9.8 changelog— docs-only, no code changeDrop the framework-owned postcss package.json override— packaging cleanup
What changed
Package versions:
- React Router: 7.18.2 → 8.3.0 (
react-router,@react-router/node,@react-router/dev,@react-router/fs-routes— all share one pin) - Node minimum: 22.12.0 → 22.22.0, matching React Router 8's
engines.node - Removed
react-router-dom, which no longer exists upstream: v7 collapsed the DOM APIs intoreact-router/domand keptreact-router-domas a convenience re-export; v8 drops it
Compatibility — nothing else needed to move:
- React 19.2.8 (already pinned) satisfies React Router 8's React 19.2.7+ peer requirement
- Vite 8.0.16 (already pinned) satisfies the Vite 7+ floor
- No
rxconfig.pyor app code changes needed. Reflex sets nofuture.v8_*flags, so the v8 defaults those flags now enable — middleware, pass-through requests, trailing-slash-aware data requests, the Vite Environment API build path, andsplitRouteModules— all apply as-is - Generated route modules export only a default component (no
meta,loader, orhasErrorBoundary), so themetadata→loaderDatarename, theRouterContextProvidercontext change, and thehasErrorBoundaryremoval are all inapplicable future.unstable_optimizeDepsremains a valid v8 config key
Changelog correction (commit 2) — why packages/reflex-base/CHANGELOG.md shows a diff in an already-released section:
The released 0.9.8 notes claim vite: 8.0.16 → 8.2.0, but that bump was reverted before #6678 merged — vite 8.2.0 reintroduces a memoized-component re-render regression, which is why the pin still carries a comment holding it at 8.0.16. The news fragment was not updated alongside the revert, so the published notes describe an upgrade that never shipped. This drops that line, and drops the "(also satisfying vite 8.2.0's ^8.5.23)" aside from the same entry's postcss rationale: shipped vite is 8.0.16, which requires only postcss@^8.5.15. The override's actual reason — holding transitive resolutions at the ≥ 8.5.18 advisory floor — is unchanged and retained.
postcss override removal (commit 3):
postcss is pinned directly in DEV_DEPENDENCIES (8.5.23), and a top-level pin already satisfies and dedupes every transitive requirer: autoprefixer (peer ^8.1.0), postcss-import (peer ^8.0.0), and vite's own ^8.5.15. Verified — with the override removed, a fresh app and the docs app each resolve exactly one postcss@8.5.23, so the advisory floor is held by the pin alone.
The override was also strictly worse to carry, which is the real motivation: update_package_json_overrides merges framework overrides into a project's reflex.lock/package.json and never removes entries, so once shipped an override cannot be retired by a later release — and it would actively conflict the next time the postcss dev pin moves past the frozen override value. OVERRIDES is kept as an empty mapping (the mechanism stays wired up and unit-tested).
Projects that already installed 0.9.8 keep an inert "postcss": "8.5.23" entry there. Confirmed by simulating that upgrade: the persisted entry survives, the build succeeds, and postcss still resolves to 8.5.23, so it is a no-op until removed by hand.
Breaking changes for users
Node 22.22.0+ is now required. This hard-errors only for users who prefer npm over Reflex's managed bun; otherwise it warns.
react-router-dom is gone. A custom component declaring library = "react-router-dom" must move to react-router — with one exception: RouterProvider and HydratedRouter live in react-router/dom, not react-router.
import { Link, useNavigate, Outlet } from "react-router"; // most APIs
import { RouterProvider, HydratedRouter } from "react-router/dom"; // these two
Existing projects have the stale react-router-dom entry pruned from package.json automatically on the next install, by the existing stale-package logic in _install_frontend_packages.
Testing
- New unit tests (
tests/units/reflex_base/constants/test_installer.py) pinning the upstream Node/React/Vite floors, which otherwise live only in react-router's manifest, so a future partial bump fails loudly instead of at install time - Unit suite: 7087 passed;
ruffandpyrightclean - Built and ran a multi-page app in dev and prod — state, websocket event round trip,
Linkclient-side navigation, dynamic route, 404 fallback — plus the embed entry'screateMemoryRouter/RouterProviderpath mounted into a host page - Full playwright integration suite: 245/245 green
- Confirmed the
patchReactRouterHmrRuntimevite plugin (#6774) still applies on 8.3.0: its regex still matches the v8 HMR runtime, its "hmr runtime changed" skip warning never fires, and the wedge it fixes does not recur. That patch fails open, so a silent regex miss would have quietly reintroduced the bug. - Lockfiles regenerated
Remaining vite/rolldown build warnings (advancedChunks, rollupOptions.jsx) and the prod dynamic-route 404 status were checked against a 7.18.2 baseline and reproduce identically — they are pre-existing, not upgrade fallout.
Documentation
News fragments added in both news/ and packages/reflex-base/news/ for the react-router upgrade (breaking) and the postcss override removal (misc).