43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import { renderToString } from "react-dom/server";
|
|
import { StaticRouter } from "react-router-dom";
|
|
import { AuthProvider } from "./providers/AuthProvider";
|
|
import { SsrDataProvider } from "./ssr/context";
|
|
import { matchSsrRoute, type ApiFetch } from "./ssr/routes";
|
|
import type { SsrData, SsrHead } from "./ssr/types";
|
|
import App from "./app";
|
|
|
|
type RenderResult =
|
|
/** Not a server-rendered route - serve the client-only shell. */
|
|
| { kind: "spa" }
|
|
/** Server-rendered route whose content is missing - shell, with a 404. */
|
|
| { kind: "not-found" }
|
|
| { kind: "ssr"; html: string; data: SsrData; head: SsrHead };
|
|
|
|
async function render(url: string, api: ApiFetch): Promise<RenderResult> {
|
|
const pathname = new URL(url, "http://ssr.invalid").pathname;
|
|
const matched = matchSsrRoute(pathname);
|
|
if (!matched) return { kind: "spa" };
|
|
|
|
const loaded = await matched.route.load(api, matched.params);
|
|
if (!loaded) return { kind: "not-found" };
|
|
|
|
// The visitor's session is deliberately not resolved here: auth-dependent
|
|
// chrome renders logged-out on the server and AuthProvider fills it in after
|
|
// hydration, which keeps these pages cacheable and hydration-safe.
|
|
const html = renderToString(
|
|
<SsrDataProvider data={loaded.data}>
|
|
<AuthProvider>
|
|
<StaticRouter location={url}>
|
|
<App />
|
|
</StaticRouter>
|
|
</AuthProvider>
|
|
</SsrDataProvider>,
|
|
);
|
|
|
|
return { kind: "ssr", html, data: loaded.data, head: loaded.head };
|
|
}
|
|
|
|
export { render };
|
|
export { defaultHead } from "./ssr/routes";
|
|
export type { RenderResult };
|