SPA Hydration (client.jsx)
Explore the core client hydration entrypoint that bootstraps the Dinou SPA router and handles transition animations, scroll restoration, and cache mappings.
Key File Location: ./dinou/core/client.jsx💡 Overview
The client.jsx module is the browser-side application loader. It hydrates the React component tree over the pre-rendered HTML document, initializes the dynamic client router context, listens for browser navigation events, and coordinates Server Functions executions.
📊 Physical File Structure
The file defines helper cache stores, RSC fetch wrappers, and the hydration entry:
🔗 1. Imports & Core Modules
The script pulls in core React hydration hooks, the RSC stream deserializer createFromFetch, the global RouterContext context, path utility helper functions, and the remote Function proxy binder:
import {
use,
useState,
useEffect,
useTransition,
useLayoutEffect,
useMemo,
Component,
} from "react";
import { createFromFetch } from "@roggc/react-server-dom-esm/client";
import { hydrateRoot } from "react-dom/client";
import { RouterContext } from "./navigation.js";
import { resolveUrl, isExternalUrl } from "./navigation-utils.js";
import { createServerFunctionProxy } from "./server-function-proxy.js";💾 2. Global Module State
To coordinate transitions without memory leaks or duplicate rendering cycles, several static cache variables are initialized at the module-level scope:
cache: A map storing the URL path strings to active Flight payload request promises. This is key to preventing React from launching infinite network loops on component evaluation.scrollCache: Stores the vertical scroll offset pixel coordinate for each visited path. This allows the layout engine to recover scroll coordinates on browser history PopState movements.
const cache = new Map();
const scrollCache = new Map();
const getCurrentRoute = () => window.location.pathname + window.location.search;⚙️ 3. Pure Helpers & RSC Fetches
Defines helper methods that analyze URL paths and construct fetch requests:
isHashChangeOnly(finalPath): Compares the target path to the current URL pathname and search parameters. If only the hash (anchor fragment id) changes, it bypasses network actions, allowing standard browser scroll behavior.getRSCPayload(rscKey, isPrefetch): Performs the GET request to/____rsc_payload____to fetch the server-rendered component Flight binary. It parses special headers likex-rsc-redirect(triggering redirection replacements) and wraps the response with React'screateFromFetchparser.
const isHashChangeOnly = (finalPath) => {
const targetUrl = new URL(finalPath, window.location.origin);
const normalize = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
const targetPath = normalize(targetUrl.pathname);
const currentPath = normalize(window.location.pathname);
return (
targetPath + targetUrl.search === currentPath + window.location.search &&
targetUrl.hash !== ""
);
};
const getRSCPayload = (rscKey, isPrefetch = false) => {
const url = rscKey.split("::")[0];
if (cache.has(url)) return cache.get(url);
let payloadUrl;
if (window.__DINOU_USE_OLD_RSC__ || window.__DINOU_USE_STATIC__) {
payloadUrl = window.__DINOU_USE_OLD_RSC__
? window.__DINOU_USE_STATIC__
? "/____rsc_payload_old_static____" + url
: "/____rsc_payload_old____" + url
: window.__DINOU_USE_STATIC__
? "/____rsc_payload_static____" + url
: "/____rsc_payload____" + url;
window.__DINOU_USE_OLD_RSC__ = false;
window.__DINOU_USE_STATIC__ = false;
} else {
payloadUrl = "/____rsc_payload____" + url;
}
const buildId = window.__DINOU_BUILD_ID__;
if (buildId) {
payloadUrl += (payloadUrl.includes("?") ? "&" : "?") + "buildId=" + buildId;
window.__DINOU_BUILD_ID__ = undefined;
}
const promise = createFromFetch(
fetch(payloadUrl).then((res) => {
if (res.headers.has("x-rsc-redirect")) {
const redirectUrl = res.headers.get("x-rsc-redirect");
cache.delete(url);
if (!isPrefetch) {
if (window.__DINOU_ROUTER_NAVIGATE__) {
window.__DINOU_ROUTER_NAVIGATE__(redirectUrl, { replace: true });
} else {
window.location.href = redirectUrl;
}
}
return new Promise(() => {});
}
return res;
}),
{
callServer: async (id, args) => createServerFunctionProxy(id)(...args)
}
);
cache.set(url, promise);
return promise;
};🚏 4. The Router Component
The root component mounts the RouterContext.Provider, coordinates navigation transitions, intercepts click captures, and triggers DOM hydration:
function Router() {
const [route, setRoute] = useState(getCurrentRoute());
const [isPopState, setIsPopState] = useState(false);
const [isPending, startTransition] = useTransition();
const [version, setVersion] = useState(0);
const [navError, setNavError] = useState(null);
const navigate = (href, options = {}) => {
const finalPath = resolveUrl(href, window.location.pathname);
if (isHashChangeOnly(finalPath)) {
if (options.replace) history.replaceState(null, "", finalPath);
else history.pushState(null, "", finalPath);
const hash = new URL(finalPath, window.location.origin).hash;
const element = document.getElementById(hash.replace("#", ""));
if (element) element.scrollIntoView({ behavior: "auto" });
return;
}
if (options.fresh) cache.delete(finalPath);
scrollCache.set(window.location.pathname + window.location.search, window.scrollY);
if (options.replace) history.replaceState(null, "", finalPath);
else history.pushState(null, "", finalPath);
startTransition(() => {
setIsPopState(false);
setRoute(finalPath);
setNavError(null);
});
};
// Expose hooks & Hijack click/popstate...
// Scroll Restoration useLayoutEffect logic...
const rscKey = route + "::" + version;
const content = navError ? getErrorRSCPayload(route, navError) : getRSCPayload(rscKey);
const contextValue = useMemo(() => ({
url: route, navigate, back: () => history.back(), forward: () => history.forward(),
refresh: () => { cache.delete(route); startTransition(() => setVersion(v => v + 1)); },
isPending
}), [route, isPending]);
return (
<RouterContext.Provider value={contextValue}>
<ErrorBoundary key={navError ? "error" : "normal"} onError={setNavError}>
{use(content)}
</ErrorBoundary>
</RouterContext.Provider>
);
}
hydrateRoot(document, <Router />);Core Router Mechanics
- Click hijacking: Registers a global listener that captures left-clicks on standard links, checking if they are relative workspace routes. If valid, it invokes
preventDefault()and forwards the path to thenavigate()handler. - React Transitions: Path updates are wrapped in React 19's
startTransition. While the new Flight stream compiles, the layout states remain active (reducing visual stutters). - Hydration Bootstrap: Calls React's
hydrateRoot(document, <Router />)to take ownership of the DOM root without requiring parent wrap tags.
📦 Webpack Variant (client-webpack.jsx)
When using Webpack instead of Rollup/ESM as the bundle builder, Dinou swaps the hydration entry to client-webpack.jsx.
The Only Difference: While client.jsx loads React Server DOM ESM modules, the Webpack variant loads Webpack's client parser bindings:
// In client-webpack.jsx
import { createFromFetch } from "react-server-dom-webpack/client";
import { createServerFunctionProxy } from "./server-function-proxy-webpack.js";This ensures compatibility with Webpack's module ID resolve matrices while preserving the exact same layout tree, routing transition, and scroll caching logic.