Skip to content

feat: add hydrate_fallback API for the page hydration window#6630

Open
adhami3310 wants to merge 3 commits into
mainfrom
khaleel/eng-9724-add-hydrate_fallback-api-for-the-page-hydration-window
Open

feat: add hydrate_fallback API for the page hydration window#6630
adhami3310 wants to merge 3 commits into
mainfrom
khaleel/eng-9724-add-hydrate_fallback-api-for-the-page-hydration-window

Conversation

@adhami3310

Copy link
Copy Markdown
Member

What

Adds an API to render a component during the React Router hydration window instead of a blank white page.

  • rx.App(hydrate_fallback=...) β€” a Component | ComponentCallable compiled to React Router's HydrateFallback export in root.jsx. Rendered inside the document Layout, so it inherits theme/color-mode context.
  • Config.hydrate_fallback (settable via the REFLEX_HYDRATE_FALLBACK env var) β€” a dotted import path to a no-arg callable returning a component, used when App.hydrate_fallback is not set. Same resolution pattern as extra_overlay_function.
  • Precedence: App.hydrate_fallback wins over the hydrate_fallback config.
app = rx.App(hydrate_fallback=rx.center(rx.spinner()))
# or, no code:  REFLEX_HYDRATE_FALLBACK=my_app.components.loading

Scope / limitations

  • HydrateFallback only covers the hydration window β€” after the JS bundle loads and React mounts, until hydration/route loaders finish. It does not cover the pre-JS bundle-download window.
  • In production (SPA mode) React Router prerenders the fallback into index.html, so it shows immediately; in dev it appears once the dev bundle mounts.

Implementation

  • reflex/app.py: App.hydrate_fallback field; _resolve_hydrate_fallback() (App field β†’ config import path); shared _resolve_import_path / _component_from_import_path helpers β€” extra_overlay_function now uses the latter too, switched from __import__ to importlib.import_module so nested module paths resolve correctly (the integration test's import path was corrected accordingly).
  • packages/reflex-base/.../config.py: Config.hydrate_fallback field.
  • Compiler: app_root_template emits the HydrateFallback export; threaded through _compile_app / compile_app_root / compile_app.

Tests

Codegen (emit/omit), config + env path, App-over-config precedence, and the resolver helpers. Full unit suite passes (77.25% coverage); ruff + pyright clean.

Closes ENG-9724

Render a component during the React Router hydration window instead of a
blank white page.

- rx.App(hydrate_fallback=...): a Component/ComponentCallable compiled to
  React Router's HydrateFallback export in root.jsx (inside the document
  Layout, so it gets theme/color-mode context).
- Config.hydrate_fallback (env: REFLEX_HYDRATE_FALLBACK): dotted import path
  to a no-arg callable returning a component, used when App.hydrate_fallback
  is unset. App field takes precedence.

Factor the import-path resolution shared with extra_overlay_function into
_resolve_import_path / _component_from_import_path, switching from __import__
to importlib.import_module so nested module paths resolve correctly.

Closes ENG-9724
@adhami3310 adhami3310 requested a review from a team as a code owner June 8, 2026 22:16
@linear-code

linear-code Bot commented Jun 8, 2026

Copy link
Copy Markdown

ENG-9724

@codspeed-hq

codspeed-hq Bot commented Jun 8, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

βœ… 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing khaleel/eng-9724-add-hydrate_fallback-api-for-the-page-hydration-window (81c033a) with main (6f5c80b)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (e4a9365) during the generation of this report, so 6f5c80b was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces rx.App(hydrate_fallback=...) and a Config.hydrate_fallback env-var path (REFLEX_HYDRATE_FALLBACK) to render a component during React Router's hydration window instead of a blank page. It also fixes a latent bug in extra_overlay_function by replacing __import__ with importlib.import_module, which correctly resolves nested module paths instead of returning the top-level package.

  • New App.hydrate_fallback field: Resolved via into_component (fail-fast) and threaded through compile_app β†’ _compile_app β†’ app_root_template, which emits a named export function HydrateFallback() block when a fallback is present; React Router v7 framework mode automatically wraps it in the root Layout export.
  • New _resolve_import_path / _component_from_import_path helpers: Both hydrate_fallback and extra_overlay_function now share these helpers; the importlib switch and single-segment guard deliver clearer diagnostics than the old __import__ approach.
  • Config + env-var path: BaseConfig.hydrate_fallback follows the existing REFLEX_ prefix convention; App-field takes precedence over config with intentionally different error semantics (fail-fast vs. graceful-degrade) documented in the resolver.

Confidence Score: 5/5

Safe to merge. The change is additive: no existing compilation paths are altered unless a hydrate_fallback is explicitly configured.

All changed code paths are additive β€” hydrate_fallback defaults to None at every layer, so builds without a fallback produce identical output to before. The importlib refactor of extra_overlay_function is a transparent correctness fix with a corrected integration test. The new helpers are well-tested with success, error, and edge-case coverage.

No files require special attention.

Important Files Changed

Filename Overview
reflex/app.py Adds App.hydrate_fallback field, _resolve_hydrate_fallback() method, and two helper functions; refactors extra_overlay_function to share the new helpers. The importlib.import_module switch correctly resolves nested sub-module paths.
reflex/compiler/compiler.py Threads hydrate_fallback through _compile_app β†’ compile_app_root β†’ compile_app. Correctly merges imports, custom codes, and dynamic imports from the fallback component into the shared app-root bundle.
packages/reflex-base/src/reflex_base/compiler/templates.py Adds hydrate_fallback_render/hooks parameters to app_root_template and emits a named export function HydrateFallback() block at module level when a fallback is present.
packages/reflex-base/src/reflex_base/config.py Adds hydrate_fallback: str
tests/units/test_app.py Comprehensive unit tests covering codegen emit/omit, config-path resolution, App-over-config precedence, and resolver error cases.
tests/integration/test_extra_overlay_function.py Corrects the extra_overlay_function import path to work properly with the new importlib.import_module resolution.
tests/units/compiler/test_compiler.py Adds two compiler-level tests verifying HydrateFallback is absent by default and present with the correct content when configured.

Reviews (2): Last reviewed commit: "fix: clearer error for single-segment hy..." | Re-trigger Greptile

Comment thread reflex/app.py
Comment thread reflex/app.py
Guard _resolve_import_path against paths with no dot (module would be
empty, yielding an opaque 'Empty module name' error). Document the
deliberate fail-fast vs graceful-degradation split between the App-field
and config resolution paths.

Addresses review feedback on #6630.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant