github reflex-dev/reflex v0.9.9a1

pre-release5 hours ago

Breaking Changes

  • pip install reflex no longer installs pydantic; pydantic model support activates when it is installed. Use the new reflex[pydantic] extra (or reflex[db]) to keep it. (#6786)
  • The compiled frontend now targets React Router 8.3.0 (from 7.18.2), and Reflex requires Node 22.22.0 or newer as a result. Apps on the default generated setup need no rxconfig.py or app code changes — Reflex sets no future.v8_* flags, and the v8 defaults those flags now enable (middleware, pass-through requests, trailing-slash-aware data requests, the Vite Environment API build path, and splitRouteModules) are all compatible with the generated app. One change is required if you wrote a custom component against react-router-dom: that package no longer exists upstream and is no longer installed, so library = "react-router-dom" must become react-router (or react-router/dom for RouterProvider/HydratedRouter). (#6854)

Features

  • The current App, the loaded Config, @rx.page registrations, and the bundled-library registry are now scoped to the active RegistrationContext instead of module-level globals, so multiple apps (and test harnesses) can coexist in one process without leaking registrations into each other. (#6382)
  • Report state deltas the frontend cannot process back to the backend via a new client_error socket event, logging an actionable error in the terminal instead of failing silently. A frontend/backend state mismatch is fatal for the session: further events stop until the page is reloaded after the frontend is rebuilt or api_url is corrected. (#6827)
  • Framework logging now flows through standard python logging with per-module loggers (reflex_base.utils.log, re-exported as reflex.utils.log), bootstrapped on import reflex. Rich colored output is preserved at the sink, and REFLEX_LOG_JSON emits machine-readable JSON-lines records. --loglevel critical no longer prints the system-info banner (broken LogLevel string-compare ordering fixed). (#6863)
  • The reflex CLI and runtime now log through standard python logging per-module loggers, and a new --json CLI flag (equivalent to REFLEX_LOG_JSON) emits machine-readable JSON-lines records for agent consumption. (#6865)
  • reflex deploy accepts --min-instances and --max-instances to set the autoscaling bounds of an app deployed to Google Cloud. Omitted bounds are left unchanged. (#6884)
  • reflex deploy gains --gcp-connection, to pick which of your organization's connected GCP accounts an app deploys through; --full-deploy, to serve the frontend from the provider's own container instead of Reflex's CDN; and --strategy, which was previously only settable in the config file. (#6908)
  • The compiled frontend now names what React DevTools shows. Every memoized component carries a displayName taken from the Python component class or @rx.memo function it was generated from, instead of rendering as Anonymous; every generated context (ColorModeContext, UploadFilesContext, DispatchContext, EventLoopContext, ThemeContext, and one per state) is named, so the provider stack reads as StateContext(reflex___state____state.my_state).Provider rather than an unlabelled Context.Provider; each page is labelled with its route (Component(blog/[slug])) instead of a bare Component; and client-only (NoSSRComponent) wrappers render as ClientSide(<Tag>). (#6945)

Bug Fixes

  • Stale on_load work no longer blocks or outlives a page navigation: a newer navigation for the same client now cancels the previous page's unfinished on_load event chain. (#6593)
  • A [[...splat]] catchall route no longer matches paths that merely share its prefix — posts/[[...splat]] matched /postsomething as well as /posts and its descendants, so the wrong page's on_load events could fire. (#6790)
  • Ensure state manager instances use isolated internal locks instead of sharing one lock across instances. (#6830)
  • Qualify dict annotations on BaseState that were shadowed by BaseState.dict, so type checkers resolve them to the builtin. (#6846)
  • reflex run now pre-enables the development export condition for the dev server via NODE_OPTIONS/BUN_OPTIONS. react-router 8's CLI otherwise re-executes itself to enable the condition, which bun does not honor on installs without node, tripping the CLI's restart guard and exiting the dev server (restartWithMergedOptions() was called, but the process has already been restarted). (#6857)
  • Pin REFLEX_ENV_MODE to dev when a dev AppHarness initializes, mirroring reflex run. Previously an AppHarnessProd in the same process leaked REFLEX_ENV_MODE=prod via export(), so later dev harnesses compiled with route prerendering enabled and the dev server served prerendered page HTML; on pages with hydration hazards this failed hydration on every load, and events dispatched during the hydration-recovery window were dropped (the vite 8.2.0 test_memo full-suite failures). (#6857)
  • Cache event handler annotations before runtime state-class patches can shadow builtin names on Python 3.14. (#6890)
  • rx.script head updates now flush synchronously instead of via react-helmet's requestAnimationFrame batching, fixing intermittently missing script tags after hydration (flaky "scripts not loaded" failures). (#6905)
  • Fixed a race where a finishing background task could silently discard state updates made by a concurrently running event handler before they reached the frontend, leaving the UI stale until the next write. Background handlers that never enter async with self still emit their delta, now computed under the state lock. (#6920)
  • AppHarness starts the frontend dev server with the development export condition enabled, fixing "Frontend did not start" on node-less (bun-only) installs where react-router's dev CLI restart guard trips. (#6931)
  • Adding a page no longer raises a spurious RouteValueError when a static segment lines up with another route's dynamic segment (e.g. /posts/all/[x] alongside /posts/[id]). React Router resolves such siblings in favor of the static one, so only two differently named dynamic segments at the same position conflict. The check was also order-dependent: it only tripped when the bracket-carrying route was added second. (#6953)

Performance

  • Remove the per-update asyncio.create_task wrapper in EventNamespace.emit_update, cutting scheduling overhead roughly in half for every outgoing state update while keeping the event-loop tick that flushes interim updates before a sync handler resumes. (#6734)
  • Dev mode no longer pays for React's per-element owner-stack capture: navigation clicks in a large app dropped from ~350ms to ~83ms of main-thread CPU (5.6x prod down to ~1.3x). In exchange React.captureOwnerStack() returns no owner frames in dev, which affects React DevTools' owner-stack view and custom error overlays built on that API; set REFLEX_REACT_OWNER_STACKS=1 to restore them. (#6905)
  • @rx.memo components with props bound to state are now auto-memoized at the call site: the state hooks (and event-handler callbacks) those props need compile into a generated wrapper component instead of the page module. A state change re-renders that wrapper rather than the whole page, and React's memo stops there unless one of the prop values actually changed — so binding a Var at the call site scopes an expensive component to exactly the state it reads, instead of coupling it to the page. (#6949)

Documentation

  • Documented the provider, gcp_connection and full_deploy cloud config settings, including which settings a Google Cloud target ignores and why full_deploy is left unset rather than false by default. (#6908)

Miscellaneous

  • The generated package.json no longer carries a framework-owned postcss override. The pinned postcss dev dependency already forces a single resolved copy for every transitive requirer, so the override was redundant — and because framework overrides are merged into reflex.lock/package.json and never removed from it, an unnecessary one cannot be retired by a later release. Projects that already installed 0.9.8 keep an inert "postcss": "8.5.23" override in reflex.lock/package.json; it matches the dev-dependency pin, so it changes nothing today and can be deleted by hand. (#6854)
  • Upgrade the locked dev tooling: ruff 0.15.12 -> 0.16.2, pyright 1.1.408 -> 1.1.411, typer 0.25.1 -> 0.27.1. Property docstrings are now noun phrases rather than "Get the ..." / "Return the ..." (ruff's new D421), and typer.main.get_command() is cast to click.Command because typer 0.27 vendors its own copy of click. (#6893)
  • The reflex deploy command implementation moved out of the reflex package into reflex-hosting-cli, so cloud code is no longer shipped inside the framework. Flags and behavior are unchanged, and reflex-hosting-cli remains a dependency of reflex, so reflex deploy and reflex cloud stay available out of the box. If the package is not installed, these commands now report which package to install instead of failing with a missing-command error. (#6924)

Don't miss a new reflex release

NewReleases is sending notifications on new releases.