Server-Side Rendering

Inertia Rust supports server-side rendering. Enabling SSR gives your website better SEO, since the very first page will be server-side rendered. Thereafter, they’ll be ordinary SPA pages.

Note: Node.js must be available in order to run the Inertia SSR server.

Enabling SSR is a very simple task. However, you must make few changes in your code so that the Node.js process is correctly iniitialized and killed — otherwise, the Node.js process would be left running, stopping further servers from running on the port it occupies.

First of all, enable SSR in your Inertia configs:

// src/config/inertia.rs
use super::vite::initialize_vite;
use inertia_rust::{
    template_resolvers::ViteHBSTemplateResolver, Inertia, InertiaConfig, InertiaError,
-   InertiaVersion,
+   InertiaVersion, SsrClient
};
use std::io;

pub async fn initialize_inertia() -> Result<Inertia, io::Error> {
    let vite = initialize_vite().await;
    let version = vite.get_hash().unwrap_or("development").to_string();
    let dev_mode = *vite.mode() == ViteMode::Development;

    let resolver = ViteHBSTemplateResolver::builder()
        .set_vite(vite)
        .set_template_path("www/root.hbs") // the path to your root handlebars template
        .set_dev_mode(dev_mode)
        .build()
        .map_err(InertiaError::to_io_error)?;

    Inertia::new(
        InertiaConfig::builder()
            .set_url("http://localhost:3000")
            .set_version(InertiaVersion::Literal(version))
            .set_template_resolver(Box::new(resolver))
+           .enable_ssr()
+           // `set_ssr_client` is optional. If not set, `SsrClient::default()` will be used,
+           // which is is "127.0.0.1:13714"
+           .set_ssr_client(SsrClient::new("127.0.0.1", 1000))
            .build(),
    )
}
// src/main.rs
use std::sync::{Arc, OnceLock};
use actix_web::{dev::Path, web::Data, App, HttpServer};
use config::inertia::initialize_inertia;

mod config;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenvy::dotenv().ok();
    env_logger::init();

    // starts a Inertia manager instance.
    let inertia = initialize_inertia().await?;
    let inertia = Data::new(inertia);
+   let inertia_clone = inertia.clone();

-   HttpServer::new(move || App::new().app_data(inertia.clone()))
-       .bind(("127.0.0.1", 3000))?
-       .run()
-       .await

+   let server = HttpServer::new(move || App::new().app_data(inertia_clone.clone()))
+       .bind(("127.0.0.1", 3000))?;

+   // Starts a Node.js child process that runs the Inertia's server-side rendering server.
+   // It must be started after the server initialization to ensure that the http server won't
+   // panic and shutdown without killing the Node.Js process.
+   let node = inertia.start_node_server("path/to/your/ssr.js".into())?;
+
+   let server = server.run().await;
+   let _ = node.kill().await;+
+
+   return server;
}

Indeed, you can replace let _ = node.kill().await; with std::mem::drop(node.kill()), but .awaiting on it guarantees that the process has been killed before shutting down.

Inertia always inserts a view data property isSsr (or even is_ssr), which is a boolean value representing if the page has been server-side rendered or not.

You might use it on your app.tsx to conditionally hydrate or create your front-end according to the response being or not SSRendered.

Add the following meta tag on your root template’s head element:

<meta name="ssr" content="{{ view_data.is_ssr }}">

Then, in your app.ts|js|tsx|jsx file, add the follow condition:

import "./app.scss";

import { createInertiaApp } from "@inertiajs/react";
import { createRoot, hydrateRoot } from "react-dom/client";

createInertiaApp({
  // ...
  setup({ el, App, props }) {
    const isSSR = document.head
      .querySelector("meta[name='ssr']")?
      .getAttribute("content") === "true" ?? false;

    if (isSSR) {
      hydrateRoot(el, <App {...props} />);
      return;
    }

    createRoot(el).render(<App {...props} />);
  },
});