Custom SSR
Use custom SSR to choose your own router, data loading, HTML shell, and hydration code. If you want Void to handle these for you, use Pages Routing.
Create the app
Start with a root component. This React example renders a page based on the request path:
// src/App.tsx
export default function App({ url }: { url: string }) {
return url === '/about' ? <main>About</main> : <main>Home</main>;
}Required entries
Create both entry files:
src/main.ssr.tsorsrc/main.ssr.tsxsrc/main.client.tsorsrc/main.client.tsx
Use one server entry and one client entry. Both are required.
Render API
Wrap your server renderer with defineRender() and return an HTML response. Insert assetTags.css and assetTags.preloads in <head>, and assetTags.body before </body>:
// src/main.ssr.tsx
import { renderToString } from 'react-dom/server';
import { defineRender } from 'void';
import App from './App';
export default defineRender(async (c, assetTags) => {
const url = new URL(c.req.raw.url);
const html = renderToString(<App url={url.pathname} />);
return new Response(
`<!doctype html>
<html>
<head>${assetTags.css}${assetTags.preloads}</head>
<body>
<div id="root">${html}</div>
${assetTags.body}
</body>
</html>`,
{ headers: { 'content-type': 'text/html; charset=utf-8' } },
);
});You can also export a named render(c, assetTags) function with the same signature. See defineRender for the types.
Hydrate in the browser
In the client entry, hydrate the same component:
// src/main.client.tsx
import { hydrateRoot } from 'react-dom/client';
import App from './App';
hydrateRoot(document.getElementById('root')!, <App url={window.location.pathname} />);Caching
See Revalidation for stale-while-revalidate caching of SSR pages.
API routes and static files are served before custom rendering. Unmatched non-API requests use render(c, assetTags).