inertia_wisp_ssr

Package Version Hex Docs

Server-side rendering (SSR) support for inertia_wisp. Renders Inertia.js pages on the server using a supervised pool of Node.js processes, with automatic fallback to client-side rendering if SSR fails.

Installation

Add inertia_wisp_ssr to your gleam.toml:

gleam add inertia_wisp_ssr

Quick Start

1. Add SSR to Your Supervision Tree

Add the SSR supervisor to your application’s supervision tree:

import gleam/otp/static_supervisor as supervisor
import inertia_wisp/ssr.{SsrConfig}

pub fn start_app() {
  let config = SsrConfig(
    ..ssr.default_config(),
    module_path: ssr.priv_path("my_app", "ssr/ssr.js"),
  )

  supervisor.new(supervisor.OneForOne)
  |> supervisor.add(ssr.supervised(config))
  // |> supervisor.add(other_children...)
  |> supervisor.start
}

2. Create an SSR-Enabled Layout

Create a layout factory once at startup, then use it in your handlers. This example uses nakai for type-safe HTML generation:

import gleam/list
import inertia_wisp/inertia
import inertia_wisp/ssr
import nakai
import nakai/attr
import nakai/html

fn my_layout(head: List(String), body: String) -> String {
  html.Html([attr.Attr("lang", "en")], [
    html.Head(
      list.flatten([
        [
          html.meta([attr.charset("utf-8")]),
          html.meta([
            attr.name("viewport"),
            attr.content("width=device-width, initial-scale=1"),
          ]),
        ],
        list.map(head, html.UnsafeInlineHtml),
      ]),
    ),
    html.Body([], [
      html.UnsafeInlineHtml(body),
      html.Script([attr.src("/app.js")], ""),
    ]),
  ])
  |> nakai.to_string
}

// In your main(), create the config and layout factory once at startup:
// let config = SsrConfig(
//   ..ssr.default_config(),
//   module_path: ssr.priv_path("my_app", "ssr/ssr.js"),
// )
// let layout = ssr.make_layout(config)
// Then pass `layout` through your context to handlers.

pub fn handle_request(req: Request, layout) -> Response {
  req
  |> inertia.response_builder("Home")
  |> inertia.props(my_props, encode_props)
  |> inertia.response(200, layout(my_layout))
}

3. Create Your SSR Bundle

Create priv/ssr/ssr.js with a render function that returns { head, body }:

React Example:

import { createInertiaApp } from "@inertiajs/react";
import ReactDOMServer from "react-dom/server";

const pages = import.meta.glob("./pages/**/*.jsx", { eager: true });

export async function render(page) {
  return createInertiaApp({
    page,
    render: ReactDOMServer.renderToString,
    resolve: (name) => pages[`./pages/${name}.jsx`],
    setup({ App, props }) {
      return <App {...props} />;
    },
  });
}

Vue Example:

import { createSSRApp, h } from "vue";
import { renderToString } from "vue/server-renderer";
import { createInertiaApp } from "@inertiajs/vue3";

const pages = import.meta.glob("./pages/**/*.vue", { eager: true });

export async function render(page) {
  return createInertiaApp({
    page,
    render: renderToString,
    resolve: (name) => pages[`./pages/${name}.vue`],
    setup({ App, props, plugin }) {
      return createSSRApp({ render: () => h(App, props) }).use(plugin);
    },
  });
}

Svelte Example:

import { createInertiaApp } from "@inertiajs/svelte";
import { render as renderToString } from "svelte/server";

const pages = import.meta.glob("./pages/**/*.svelte", { eager: true });

export async function render(page) {
  return createInertiaApp({
    page,
    resolve: (name) => pages[`./pages/${name}.svelte`],
    setup({ App, props }) {
      return renderToString(App, { props });
    },
  });
}

Configuration

Customize the SSR configuration:

import gleam/erlang/process
import gleam/option.{None}
import gleam/otp/static_supervisor as supervisor
import gleam/time/duration
import inertia_wisp/ssr.{SsrConfig}

let config = SsrConfig(
  module_path: ssr.priv_path("my_app", "ssr/ssr.js"),  // Absolute path to JS bundle
  name: process.new_name("my_app_ssr"),                // Pool process name
  node_path: None,                                     // Use system Node.js (or Some("/path/to/node"))
  pool_size: 8,                                        // Number of workers
  timeout: duration.seconds(5),                        // Render timeout
)

// Add to supervision tree
supervisor.new(supervisor.OneForOne)
|> supervisor.add(ssr.supervised(config))
|> supervisor.start

// Create layout factory with custom config
let layout = ssr.make_layout(config)

// Use in handlers
|> inertia.response(200, layout(my_template))

Options

Helper Functions

How It Works

SSR Flow

  1. Your handler calls inertia.response() with layout(template) from ssr.make_layout(config)
  2. The SSR layer attempts to render the page using Node.js:
    • Serializes the Inertia page data to JSON
    • Calls your ssr.js render() function via the Node.js process pool
    • Receives { head, body } from JavaScript
    • Passes the result to your template function
  3. Returns the fully-rendered HTML response

CSR Fallback

If SSR fails (Node.js error, timeout, or invalid response), the system automatically falls back to client-side rendering:

This ensures your app remains available even if SSR breaks.

Requirements

Set NODE_ENV=production so the SSR script is cached in memory. Without this, page rendering times will be very slow.

Debugging

Search Document