feat: add hydrate_fallback API for the page hydration window#6630
Conversation
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
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR adds
Confidence Score: 5/5Safe 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
Reviews (3): Last reviewed commit: "refactor: compile hydrate_fallback throu..." | Re-trigger Greptile |
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
left a comment
There was a problem hiding this comment.
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.
What
Adds an API to render a component during the React Router hydration window instead of a blank white page.
rx.App(hydrate_fallback=...)— aComponent | ComponentCallablecompiled to React Router'sHydrateFallbackexport inroot.jsx. Rendered inside the documentLayout, so it inherits theme/color-mode context.Config.hydrate_fallback(settable via theREFLEX_HYDRATE_FALLBACKenv var) — a dotted import path to a no-arg callable returning a component, used whenApp.hydrate_fallbackis not set. Same resolution pattern asextra_overlay_function.App.hydrate_fallbackwins over thehydrate_fallbackconfig.Scope / limitations
HydrateFallbackonly 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.index.html, so it shows immediately; in dev it appears once the dev bundle mounts.Implementation
reflex/app.py:App.hydrate_fallbackfield;_resolve_hydrate_fallback()(App field → config import path); shared_resolve_import_path/_component_from_import_pathhelpers —extra_overlay_functionnow uses the latter too, switched from__import__toimportlib.import_moduleso nested module paths resolve correctly (the integration test's import path was corrected accordingly).packages/reflex-base/.../config.py:Config.hydrate_fallbackfield.app_root_templateemits theHydrateFallbackexport; 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