everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
152 lines (150 loc) • 5.51 kB
JavaScript
import { readAcceptLanguage, resolveLocale } from "./locale.mjs";
import { collectHeadData } from "./router.mjs";
import { createApiClient } from "./api.mjs";
import { createAuthClient } from "./auth.mjs";
import { defaultNotFoundComponent, defaultPendingComponent, defaultQueryClient } from "./router-defaults.mjs";
import { RouterError } from "./router-error.mjs";
import { jsx } from "react/jsx-runtime";
import { createMemoryHistory, createRouter } from "@tanstack/react-router";
import { QueryClient, QueryClientProvider, dehydrate, hydrate } from "@tanstack/react-query";
import { RouterServer, createRequestHandler, renderRouterToStream } from "@tanstack/react-router/ssr/server";
//#region src/ui/router-server.tsx
/**
* SSR router factory — creates request-scoped server routers and server-side
* API/auth clients per request. Mirrors the client router shape for
* hydration consistency. The app injects its generated route tree and (for
* a byte-identical child surface) its error component.
*/
function createServerRouterModule(options = {}) {
const defaultRouteTree = options.defaultRouteTree;
const createRouter$1 = (opts) => {
const context = opts?.context;
const queryClient = context?.queryClient ?? defaultQueryClient();
const history = opts?.history ?? createMemoryHistory();
const cspNonce = context?.cspNonce;
return {
router: createRouter({
routeTree: opts?.routeTree ?? defaultRouteTree,
history,
basepath: opts?.basepath,
context: {
queryClient,
runtimeConfig: context?.runtimeConfig,
apiClient: context?.apiClient,
authClient: context?.authClient ?? createAuthClient({
runtimeConfig: context?.runtimeConfig,
cspNonce: context?.cspNonce
}),
session: context?.session,
cspNonce,
locale: context?.locale,
pluginNav: context?.pluginNav
},
...cspNonce ? { ssr: { nonce: cspNonce } } : {},
defaultPreload: "intent",
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
defaultErrorComponent: RouterError,
defaultOnCatch: (error, errorInfo) => {
console.error("[SSR] Router error boundary caught:", error, { componentStack: errorInfo.componentStack });
},
defaultNotFoundComponent,
defaultPendingComponent,
defaultPendingMinMs: 0,
dehydrate: () => {
if (typeof window === "undefined") return { queryClientState: dehydrate(queryClient) };
return { queryClientState: {} };
},
hydrate: (dehydrated) => {
if (typeof window !== "undefined" && dehydrated?.queryClientState) hydrate(queryClient, dehydrated.queryClientState);
}
}),
queryClient
};
};
const buildRouter = options.createRouter ?? createRouter$1;
const getRouteHead = async (pathname, context) => {
const history = createMemoryHistory({ initialEntries: [pathname] });
const queryClient = new QueryClient();
const runtimeConfig = context?.runtimeConfig;
if (!runtimeConfig?.hostUrl || !runtimeConfig.rpcBase) throw new Error("Missing runtime config for route head generation");
const router = createRouter({
routeTree: context?.routeTree ?? defaultRouteTree,
history,
context: {
queryClient,
runtimeConfig,
apiClient: context?.apiClient ?? createApiClient({
hostUrl: runtimeConfig.hostUrl,
rpcBase: runtimeConfig.rpcBase
}),
authClient: context?.authClient ?? createAuthClient({
runtimeConfig,
cspNonce: context?.cspNonce
}),
session: context?.session,
locale: context?.locale
}
});
return collectHeadData(router);
};
const renderToStream = async (request, renderOptions) => {
const url = new URL(request.url);
const history = createMemoryHistory({ initialEntries: [url.pathname + url.search] });
let queryClientRef = null;
const response = await createRequestHandler({
request,
createRouter: () => {
const localQueryClient = queryClientRef ?? options.createQueryClient?.() ?? defaultQueryClient();
const built = buildRouter({
history,
routeTree: renderOptions.routeTree,
basepath: renderOptions.basepath,
context: {
queryClient: localQueryClient,
runtimeConfig: renderOptions.runtimeConfig,
apiClient: renderOptions.apiClient,
authClient: createAuthClient({
runtimeConfig: renderOptions.runtimeConfig,
headers: request.headers,
cspNonce: renderOptions.cspNonce
}),
session: renderOptions.session,
cspNonce: renderOptions.cspNonce,
pluginNav: renderOptions.pluginNav,
locale: options.locale ? resolveLocale({
...options.locale,
cookie: request.headers.get("cookie") ?? "",
browserLocales: readAcceptLanguage(request.headers.get("accept-language"))
}) : void 0
}
});
queryClientRef = built.queryClient ?? localQueryClient;
return built.router;
}
})(({ request, responseHeaders, router }) => renderRouterToStream({
request,
responseHeaders,
router,
children: /* @__PURE__ */ jsx(QueryClientProvider, {
client: queryClientRef,
children: /* @__PURE__ */ jsx(RouterServer, { router })
})
}));
if (options.locale) response.headers.append("Vary", "Cookie, Accept-Language");
return {
stream: response.body,
statusCode: response.status,
headers: response.headers
};
};
return {
createRouter: createRouter$1,
getRouteHead,
renderToStream
};
}
//#endregion
export { createServerRouterModule };
//# sourceMappingURL=router-server.mjs.map