Your server will render your inertia pages to HTML. From there, your client
setup must be able to load the necessary scripts and its dependencies.
This process is highly customisable and specific to your setup. We will use React and Vite as an example, and explain the process here to make it easier for you to set things up.
If there is an inertia-server integration for your framework, setup may be already configured for you.
Step 1: Pick initial rendering strategy
When you inertia.render, the resolved page is passed to the render
function you provided when setting up inertia-server. Here, you can
encode the page to JSON and send it with some html markup so it can be hydrated on the client.
export function renderToHtml(
page: InertiaPage,
): string {
return `<!DOCTYPE html><html><body><div id="app" data-page='${JSON.stringify(page)}'></div></body></html>`;
}
You can enchance this setup further, and render the initial shell of your app with your framework’s server-side rendering features.
export async function renderToHtml(
page: InertiaPage,
): string {
const { renderToString } = await import("react-dom/server");
return `<!DOCTYPE html>${renderToString(<Root page={page} />)}`;
}
The added benefit of this approach is that not only you improve the loading experience, but also you can write your server templates as your framework’s components.
Step 2: Prepare your inertia client entry
On the client, you will initialise your inertia app according to the official docs.
For server side rednering, you will need to update your client entry to hydrate the page instead of rendering it
The tricky part after setting up your server and entry, is to coordinate your frontend assets with your server. If you render a page, you also have to send it’s assets to the client - scripts, styles, images, etc.
In simple cases, you can inline all of them in your root template,
and make sure that you serve them from your server.
export function renderToHtml(
page: InertiaPage,
): string {
return `<!DOCTYPE html>
<html>
<head>
<!-- Your assets -->
<script src="/assets/main.js"></script>
</head>
<body>
<div id="app" data-page='${JSON.stringify(page)}'></div>
</body>
</html>`;
}
This quickly becomes impractical, as your app grows and you have more assets.
The easiest way is to setup Vite to handle your frontend app. When you build it, it will generate a manifest file - a JSON object that maps your entrypoints to their corresponding assets. You then load it on the client and everything happens automatically.
In the development mode, you can use the Vite dev server to serve your assets directly. But in production, you will need to serve them from your server.
This config is partial to showcase the key parts. See examples in the
inertia-server repo for a complete reference.
export default defineConfig(({ command }) => ({
base: command === "build" ? "/assets/" : "/",
build: {
outDir: "dist/client",
manifest: true,
emptyOutDir: true,
rollupOptions: {
input: "src/ui/main.tsx",
},
},
plugins: [react(), tsconfigPaths(), tailwindcss()],
}));
export async function renderToHtml(
page: InertiaPage,
manifest: Manifest | null,
): string {
const { renderToString } = await import("react-dom/server");
// We pass manifest to the root component so it can be used to load the assets
return `<!DOCTYPE html>${renderToString(<Root page={page} manifest={manifest} />)}`;
}
export default function Root({ page, manifest }: RootProps) {
const isDev = !manifest;
const entrypoint = "src/ui/main.tsx";
const entrypointUrl = manifest?.[entrypoint]?.file
? `/assets/${manifest[entrypoint].file}`
: null;
const cssFiles = manifest?.[entrypoint]?.css ?? [];
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Inertia Server Example</title>
{cssFiles.map((css) => (
<link key={css} rel="stylesheet" href={`/assets/${css}`} />
))}
</head>
<body>
<div id="app" data-page={JSON.stringify(page)} />
{isDev ? (
<>
<script type="module" src={`${VITE_DEV_SERVER_URL}/@vite/client`} />
<script
type="module"
dangerouslySetInnerHTML={{ __html: getReactRefreshPreamble() }}
/>
<script type="module" src={`${VITE_DEV_SERVER_URL}/src/ui/main.tsx`} />
</>
) : (
entrypointUrl && <script type="module" src={entrypointUrl} />
)}
</body>
</html>
);
}