Configuration Reference

All configuration options for PhoenixPrerender

prerendered

Prerendered under the /docs scope. Generated file: docs/terms/index.html

Application Configuration

All options live under the :phoenix_prerender application key:

config :phoenix_prerender,
  enabled: false,
  output_path: "priv/static/prerendered",
  url_style: :dir_index,
  cache_control: "public, max-age=300",
  strict_paths: true,
  concurrency: System.schedulers_online(),
  bots_only: false,
  base_url: "https://example.com",
  pubsub: nil
Option Default Description
enabled false Whether the serving plug is active
output_path "priv/static/prerendered" Directory for generated HTML files
url_style :dir_index :dir_index (about/index.html) or :file (about.html)
cache_control "public, max-age=300" Cache-Control header for served pages
strict_paths true Only serve paths listed in manifest.json
bots_only false Global override: serve all prerendered pages to bots only. Prefer per-route metadata instead.
concurrency schedulers_online Parallel rendering tasks during generation
base_url "https://example.com" Base URL for sitemap.xml generation
pubsub nil PubSub server for distributed cache invalidation

Per-Route Metadata

Inside a prerender do block, dead views get prerender: true injected automatically. LiveView routes pass through unchanged — use explicit metadata to control prerendering:

Metadata Behavior
%{prerender: true} Served to all clients (default for dead views in prerender block)
%{prerender: :bots_only} Prerendered HTML served to search engine crawlers only; browsers get the live app
%{prerender: :always} Served to everyone with a fresh session and CSRF token (requires session_options)
%{isr: true} Enable per-route ISR; triggers background regeneration when stale
prerender do
  get "/about", PageController, :about                              # prerender: true (auto)
  live "/changelog", ChangelogLive                                  # not prerendered
  live "/status", StatusLive, :index,
    metadata: %{prerender: :bots_only}                              # SEO only
  live "/dashboard", DashboardLive, :index,
    metadata: %{prerender: :always, isr: true}                      # everyone + ISR
end

URL Styles

URL Path :dir_index :file
/ index.html index.html
/about about/index.html about.html
/docs/terms docs/terms/index.html docs/terms.html

Plug Options

Options passed directly to the plug override application config:

# endpoint.ex
@session_options [store: :cookie, key: "_app_key", signing_salt: "..."]

plug PhoenixPrerender.Plug,
  endpoint: MyAppWeb.Endpoint,            # required for ISR
  session_options: @session_options,       # required for :always routes
  output_path: "priv/static/prerendered",
  url_style: :dir_index,
  cache_control: "public, max-age=3600",
  strict_paths: true,
  enabled: true

session_options enables the CSRF token swap for prerender: :always routes. Without it, :always routes are served without a fresh session, which will break LiveView's WebSocket connection.

ISR Configuration

ISR is opt-in per route via metadata. Add the PageCache and Regenerator to your supervision tree, and pass the endpoint to the plug:

# application.ex
children = [
  PhoenixPrerender.PageCache,
  {PhoenixPrerender.Regenerator, endpoint: MyAppWeb.Endpoint}
]

# endpoint.ex
plug PhoenixPrerender.Plug,
  endpoint: MyAppWeb.Endpoint,
  session_options: @session_options

# config/prod.exs
config :phoenix_prerender,
  enabled: true,
  revalidate: 300  # seconds before a page is stale

# router.ex — opt in per route
prerender do
  live "/status", StatusLive, :index,
    metadata: %{prerender: :always, isr: true}
end

<.prerendered> Component

For LiveView pages with prerender: :always, use the <.prerendered> component to freeze values at prerender time. LiveView will not patch these elements after hydration.

import PhoenixPrerender.Components

def mount(_params, _session, socket) do
  # Only compute at prerender time (disconnected)
  generated_at =
    if connected?(socket),
      do: "",
      else: DateTime.utc_now() |> Calendar.strftime(...)

  {:ok, assign(socket, generated_at: generated_at)}
end

def render(assigns) do
  ~H"""
  <.prerendered id="gen-time" tag="p" class="font-mono">
    {@generated_at}
  </.prerendered>
  """
end

Attributes: id (required), tag (default: "span"), class (optional). See the /status page for a live demo.