Skip to content

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

Merged
masenf merged 4 commits into
mainfrom
khaleel/eng-9724-add-hydrate_fallback-api-for-the-page-hydration-window
Jun 9, 2026
Merged

feat: add hydrate_fallback API for the page hydration window#6630
masenf merged 4 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 (f983a71) with main (985e694)

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.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds rx.App(hydrate_fallback=...) and Config.hydrate_fallback (via REFLEX_HYDRATE_FALLBACK) to render a component during React Router's hydration window instead of a blank page. It also refactors the existing extra_overlay_function resolution into shared _resolve_import_path / _component_from_import_path helpers and fixes an __import__-vs-importlib bug that affected nested module paths.

  • App.hydrate_fallback accepts a component or callable; _resolve_hydrate_fallback() resolves it (App field takes precedence over config) and the compile pipeline styles it, wraps it in a dedicated memo module, and re-exports it as HydrateFallback from root.jsx.
  • Helper refactor: _resolve_import_path now uses importlib.import_module instead of __import__, correctly resolving nested submodule paths; the integration test's import path is corrected accordingly.
  • Tests cover codegen emit/omit, config path, App-over-config precedence, import-path edge cases, and memo resolver helpers.

Confidence Score: 5/5

Safe to merge — all changed paths are additive, the refactored helpers are backwards-compatible, and the integration test correction fixes a pre-existing latent bug.

The change is well-scoped: new fields and helpers are additive, the compile pipeline threading is correct, the memo closure captures state before any mutations, and template output is valid React Router JS. Tests cover the happy path, config fallback, precedence, and error conditions thoroughly.

No files require special attention.

Important Files Changed

Filename Overview
reflex/app.py Adds App.hydrate_fallback field and refactors overlay resolution into shared helpers; all error paths, precedence logic, and the importlib switch are correct.
reflex/compiler/compiler.py Threads hydrate_fallback_export through compile path; resolves, styles, and registers the fallback as an auto memo before memo compilation. Ordering is correct.
packages/reflex-base/src/reflex_base/compiler/templates.py Emits HydrateFallback re-export when set; blank-line behaviour preserved when not. Template output is valid React Router JS.
packages/reflex-base/src/reflex_base/components/memo.py Adds create_component_memo wrapping a standalone component in a closure. Closure correctly captures the component before any mutations.
packages/reflex-base/src/reflex_base/config.py Adds hydrate_fallback field to BaseConfig; picked up by the REFLEX_ env-var prefix convention.
tests/integration/test_extra_overlay_function.py Corrects the import path for extra_overlay_function to work with importlib.import_module semantics.
tests/units/test_app.py Comprehensive unit tests covering emit, omit, config path, precedence, and import-path edge cases.
tests/units/compiler/test_compiler.py Adds two compiler-level tests verifying HydrateFallback omit/emit behaviour.

Reviews (3): Last reviewed commit: "refactor: compile hydrate_fallback throu..." | 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.

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we can wrap the hydrate fallback in an rx.memo or a create_passthrough_component_memo, then the compiler extensions to generate the component code aren't needed. the memo machinery will write out the component to its own JS module which can be imported into root.jsx with an alias as HydrateFallback.

i think adding this extra code to the root compile function to support additionally compiling a single component inline is a bit fiddly

Instead of inline-compiling the fallback component's JSX/hooks/imports in
the root app template, wrap it in a memo so it lands in its own JS module;
root.jsx re-exports that module as HydrateFallback.

- add create_component_memo(component, name): an unregistered snapshot memo
  that renders the full component (no {children} hole, unlike the passthrough
  memo) for use where the memo is referenced without children
- compile_app registers the fallback memo in auto_memo_components and passes
  only its export name onward; _compile_app/compile_app_root/app_root_template
  drop the inline render/hooks/import merging in favor of a re-export line

Addresses review feedback on #6630.
@masenf masenf merged commit f232cf4 into main Jun 9, 2026
106 checks passed
@masenf masenf deleted the khaleel/eng-9724-add-hydrate_fallback-api-for-the-page-hydration-window branch June 9, 2026 19:16
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.

2 participants