inertia_wisp/ssr

Server-side rendering support for Inertia.js applications.

This module provides the public API for adding SSR to your Inertia handlers. It wraps your HTML template function to first attempt server-side rendering via Node.js, falling back to client-side rendering if SSR fails.

Types

Layout handler function returned by layout() and make_layout(). Takes the component name and page data JSON, returns rendered HTML.

pub type LayoutHandler =
  fn(String, json.Json) -> String

Page layout function that receives SSR head elements and body content.

  • head: List of HTML strings for the <head> section (scripts, styles, meta tags)
  • body: The rendered HTML body content
pub type PageLayout =
  fn(List(String), String) -> String

Type alias for pool names - a typed handle for pool lookup. Create pool names using process.new_name() at application startup.

pub type PoolName =
  process.Name(@internal PoolMessage)

Configuration for the SSR pool and rendering behavior.

Fields

  • module_path: Absolute path to the JavaScript SSR module (use priv_path to resolve)
  • name: Pool name for registration (default: uses default name)
  • node_path: Optional custom path to Node.js executable (default: None, uses system PATH)
  • pool_size: Number of persistent Node.js worker processes (default: 4)
  • timeout: Maximum time to wait for SSR rendering (default: 1 second)
pub type SsrConfig {
  SsrConfig(
    module_path: String,
    name: process.Name(@internal PoolMessage),
    node_path: option.Option(String),
    pool_size: Int,
    timeout: duration.Duration,
  )
}

Constructors

Values

pub fn default_config() -> SsrConfig

Create a default SSR configuration.

Uses a default pool name, “priv/ssr/ssr.js” as module path, 4 workers, and 1s timeout. For production releases, use priv_path() to resolve the module path correctly.

Example

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

Note: This function creates a new pool name each time it’s called. For multiple pools, create specific names with process.new_name() at startup.

pub fn layout(
  config: SsrConfig,
  template: fn(List(String), String) -> String,
) -> fn(String, json.Json) -> String

Wrap a template function to enable server-side rendering.

The template function receives:

  • head: List of HTML strings for the <head> section
  • body: The rendered HTML body content

If SSR fails, this automatically falls back to client-side rendering.

Example

fn my_layout(head: List(String), body: String) -> String {
  "<!DOCTYPE html><html><head>"
  <> string.join(head, "\n")
  <> "</head><body>"
  <> body
  <> "<script src='/app.js'></script></body></html>"
}

// In handler:
|> inertia.response(200, ssr.layout(config, my_layout))
pub fn make_layout(
  config: SsrConfig,
) -> fn(fn(List(String), String) -> String) -> fn(
  String,
  json.Json,
) -> String

Create a reusable layout function with SSR configuration baked in.

Call this once at startup and reuse the returned function in handlers.

Example

// At startup
let config = SsrConfig(
  ..ssr.default_config(),
  module_path: ssr.priv_path("my_app", "ssr/ssr.js"),
)
let layout = ssr.make_layout(config)

// In handler
|> inertia.response(200, layout(my_template))
pub fn priv_path(app_name: String, path: String) -> String

Resolve a path relative to an OTP application’s priv directory.

Call this at application startup to get the absolute path for SsrConfig. In Erlang releases, the priv directory location is unpredictable, so this function uses the OTP application system to resolve it correctly.

Falls back to "priv/" <> path if the application is not loaded.

Example

let module_path = ssr.priv_path("my_app", "ssr/ssr.js")
let config = SsrConfig(..ssr.default_config(), module_path: module_path)
pub fn supervised(
  config: SsrConfig,
) -> supervision.ChildSpecification(Nil)

Get a child specification for adding the SSR pool to your supervision tree.

The pool is registered under the name in config, allowing make_layout() to look it up automatically.

Example

import gleam/otp/static_supervisor as supervisor
import inertia_wisp/ssr

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.start
}
Search Document