Repository navigation
ssr wip - #6173
ssr wip#6173benedikt-bartscher wants to merge 16 commits into
Conversation
Merging this PR will not alter performance1 warning
Comparing |
[Medium impact] The change affects production rendering for apps that opt into runtime SSR. The PR is not ready to merge because production startup, page rendering, and load-handler behavior remain broken. Findings
SummaryAdds experimental, default-off
Reviews (4) · Last reviewed commit: "Choose list route params by segment type..." · Reviewed by Greptile |
| ); | ||
|
|
||
| // Other static files (favicon, sitemap, etc.) — short cache. | ||
| app.use(express.static(clientDir, { maxAge: "1h" })); |
| if (accept.includes("text/html") && !req.url.endsWith(".data")) { | ||
| return res | ||
| .setHeader("Content-Type", "text/html; charset=utf-8") | ||
| .send(shellHtml); |
There was a problem hiding this comment.
| event_handler = target_state.event_handlers[handler_attr] | ||
|
|
||
| # Execute the handler on the target substate. | ||
| result = event_handler.fn(target_state) |
| # For generators (event chains), consume them but ignore returned events. | ||
| if inspect.isgenerator(result): | ||
| for _ in result: | ||
| pass | ||
| if inspect.isasyncgen(result): | ||
| async for _ in result: | ||
| pass |
There was a problem hiding this comment.
| // (a) Full SSR render for bots — ssr-serve.js routes bot requests here. | ||
| // (b) .data requests for client-side navigation — React Router calls the | ||
| // loader to fetch route data as JSON for the next page. | ||
| // Both cases need real state data from the backend. | ||
| const backendUrl = getBackendURL(env.SSR_DATA); | ||
| try { | ||
| const res = await fetch(backendUrl, { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| ...(request.headers.get("cookie") ? { "Cookie": request.headers.get("cookie") } : {}), | ||
| }, | ||
| body: JSON.stringify({ | ||
| path: new URL(request.url).pathname, | ||
| headers: Object.fromEntries(request.headers), | ||
| }), | ||
| }); | ||
| if (res.ok) { | ||
| return res.json(); | ||
| } | ||
| } catch (e) { | ||
| console.error("SSR data fetch failed:", e); | ||
| } | ||
| return { state: null }; |
There was a problem hiding this comment.
Navigation runs load handlers twice On a client-side navigation, the
.data request reaches this loader and runs the page’s on_load handlers through /_ssr_data. The existing navigation effect also sends on_load_internal over the WebSocket. A handler with an external side effect, such as a write or notification, can therefore perform it twice for one navigation.
Upstream replaced the per-substate reducer providers with a useSyncExternalStore-backed state store and moved the boot hydrate into the socket.io CONNECT packet. The SSR state now seeds the client store (createStateStore) and backs the server store's getSnapshot, and the SSR-hydrated first connect sends a plain hydrate as its boot event in connect() instead of the removed mount-time hydrate effect in useEventLoop. The SSR template tests were updated to match.
| const boot_event = withRouterData( | ||
| first && ssrHydrated | ||
| ? ReflexEvent(`${app.state_name}.hydrate`) | ||
| : app.initialEvents(first)[0], |
There was a problem hiding this comment.
SSR data disappears on connection
For browsers receiving SSR HTML, this branch sends hydrate against the browser's separate backend session. But ssr_data ran the load handlers on a temporary state, not that session. hydrate then sends the session's full state, replacing the loaded values. It also resets client storage and leaves is_hydrated false without queuing load handlers.
Keep the browser's backend session in sync before skipping hydrate_and_load.
| if get_config().runtime_ssr: | ||
| scripts["prod"] = constants.PackageJson.Commands.PROD_SSR |
There was a problem hiding this comment.
Adding a prod script does not enable SSR for reflex run --env prod. That command still starts the Python static server, either separately or mounted on the backend. Neither path launches ssr-serve.js or reads this script, so crawlers still receive static HTML with runtime_ssr=True.
Wire the SSR server into the production launch path.
Match route params with a regex so catch-alls, omitted optional segments and encoded segments resolve like on the client, forward the query string to load handlers, strip frontend_path before routing, and only exclude top-level source dirs from the SSR export. Add news fragments.
extract_route_params splits a value into a list only for catch-all arguments, as reported by get_route_args, so a plain argument named splat such as blog/[splat] stays a string.
No description provided.