Summary
Investigate whether it's feasible and desirable for Reflex to manage hot reload internally rather than relying on vite's dev server. In this mode, Reflex would invoke bun/vite for a dev build, serve the files statically, watch for Python source changes, perform minimal recompiles, trigger incremental bun/vite rebuilds, and notify the browser to reload.
This is an investigation issue — the goal is to answer the feasibility questions and produce a recommendation, not to implement the full feature.
Background & Motivation
Today, the dev workflow involves two servers: the Reflex backend (Python/FastAPI) and the vite dev server (Node). The vite dev server handles JS bundling, HMR (hot module replacement), and serving the frontend. This works but has drawbacks:
- Two ports: The frontend runs on a different port than the backend, requiring proxy configuration and complicating deployment/debugging
- Vite controls the reload cycle: Even when Reflex finishes recompiling quickly, the user still waits for vite's HMR pipeline
- Limited control over what gets rebuilt: Reflex writes to
.web/ and vite picks up all changes via filesystem watching — there's no fine-grained signaling from Reflex to vite about what actually changed
- Overhead of vite dev server: The vite dev server has its own memory footprint and startup time
What the new compiler work enables
With the single-pass compiler (ENG-9142-ENG-9149), caching (ENG-9148), and per-module JS output for memo components, Reflex will have:
- Knowledge of exactly which JS files changed after a Python edit
- Ability to detect changes that don't impact UI code (e.g., backend-only state logic changes that don't affect component rendering)
- Granular JS output — more small files that change independently, rather than a few large files that change frequently
This makes internally-managed hot reload potentially viable.
Proposed Architecture (to investigate)
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Python FS │────▶│ Reflex │────▶│ bun/vite │
│ Watcher │ │ Compiler │ │ (build) │
└─────────────┘ │ (minimal │ └──────┬──────┘
│ recompile) │ │
└──────────────┘ │
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ Detect: │ │ Static │
│ UI change? │ │ file │
│ or backend │ │ server │
│ only? │ │ (Reflex) │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────────────────▼──────┐
│ Browser: reload JS / refresh │
└────────────────────────────────┘
- Python file watcher — Reflex watches tracked Python modules for changes
- Minimal recompile — Only recompile affected pages/modules (using caching from ENG-9148)
- Change classification — Determine if the change affects UI code (component tree) or is backend-only (state logic, event handlers)
- Incremental build — Invoke bun/vite for a production-style build, but only on changed files
- Static serving — Reflex serves the built files directly (single port)
- Reload notification — Push a reload signal to the browser via the existing websocket connection
Key questions to answer
- Is vite's incremental build fast enough? Vite's
vite build is slower than its dev mode HMR. With granular JS files (from memo provenance tracking), is the incremental build time acceptable? What's the typical delta?
- Is this actually faster than letting vite do HMR? If the new compiler outputs many small files that rarely change, vite's HMR might already be fast enough that managing it ourselves adds complexity without benefit.
- Can we do module-level HMR without vite? Instead of a full page reload, can Reflex inject only the changed JS module? This would require implementing a basic HMR runtime — is it worth it?
- What about CSS/asset changes? Vite handles CSS HMR, PostCSS processing, asset optimization. If we bypass vite's dev server, we need to handle these ourselves or accept page reloads for CSS changes.
- Backend-only change detection: For changes that only affect state logic (event handler implementations), no browser reload is needed at all — the websocket reconnects to the updated backend. How reliably can we detect this?
- Single-port serving: What changes are needed to serve the frontend statically from the Reflex backend? FastAPI's
StaticFiles mount should work, but are there path/routing considerations?
- Do we even need this? If the compiler outputs more granular files and vite's HMR becomes fast enough as a result, the complexity of managing our own hot reload may not be justified. The investigation should honestly assess whether the current vite dev server approach, combined with better compiler output, is already good enough.
Investigation Deliverables
Related Issues
- ENG-9142-ENG-9149 — Single-pass compiler (enables caching and minimal recompile)
- ENG-9148 — Selective page recompilation and caching
- Memo provenance tracking (enables per-module JS output)
Notes
- This investigation should be done after the core compiler improvements land, since its viability depends on those capabilities (especially caching and granular output).
- esbuild (used by vite internally) supports an incremental build API that could be used directly, bypassing vite's higher-level abstractions. This might be worth exploring.
- The React Router framework mode that Reflex uses may have opinions about how HMR works — investigate compatibility.
- Even if full single-port mode isn't pursued, the "detect backend-only changes and skip browser reload" optimization is independently valuable and lower risk.
Summary
Investigate whether it's feasible and desirable for Reflex to manage hot reload internally rather than relying on vite's dev server. In this mode, Reflex would invoke bun/vite for a dev build, serve the files statically, watch for Python source changes, perform minimal recompiles, trigger incremental bun/vite rebuilds, and notify the browser to reload.
This is an investigation issue — the goal is to answer the feasibility questions and produce a recommendation, not to implement the full feature.
Background & Motivation
Today, the dev workflow involves two servers: the Reflex backend (Python/FastAPI) and the vite dev server (Node). The vite dev server handles JS bundling, HMR (hot module replacement), and serving the frontend. This works but has drawbacks:
.web/and vite picks up all changes via filesystem watching — there's no fine-grained signaling from Reflex to vite about what actually changedWhat the new compiler work enables
With the single-pass compiler (ENG-9142-ENG-9149), caching (ENG-9148), and per-module JS output for memo components, Reflex will have:
This makes internally-managed hot reload potentially viable.
Proposed Architecture (to investigate)
Key questions to answer
vite buildis slower than its dev mode HMR. With granular JS files (from memo provenance tracking), is the incremental build time acceptable? What's the typical delta?StaticFilesmount should work, but are there path/routing considerations?Investigation Deliverables
vite buildwith changed files) vs HMR time for the same changes on a representative appRelated Issues
Notes