Features
Everything PhoenixPrerender provides
This page was generated at build time and is served as static HTML.
Build-Time Static Generation
Routes marked with
prerender do
are
discovered automatically. The generator renders each through the complete Phoenix endpoint pipeline
using Phoenix.ConnTest.dispatch/5,
ensuring all plugs, layouts, and components execute.
Pages are rendered concurrently via
Task.async_stream
with configurable concurrency. Default parallelism matches System.schedulers_online().
Per-Route Prerender Modes
Dead views inside
prerender do
are prerendered by default and served to everyone. LiveView routes require explicit metadata
to control how prerendered content is served:
prerender do
get "/about", PageController, :about # served to everyone
live "/changelog", ChangelogLive # not prerendered (just live)
live "/status", StatusLive, :index,
metadata: %{prerender: :bots_only} # prerendered for SEO bots only
live "/yolo", PreRenderedLive, :index,
metadata: %{prerender: :always, isr: true} # prerendered for everyone + ISR
end
| Mode | Behavior |
|---|---|
| prerender: true | Served to everyone (default for dead views) |
| prerender: :bots_only | Served only to search engine crawlers; browsers get the live app |
| prerender: :always | Served to everyone with fresh session/CSRF (requires session_options) |
Incremental Static Regeneration
ISR implements the stale-while-revalidate
pattern: stale pages are served
immediately while a background task re-renders and writes fresh HTML. Users never wait.
ISR is opt-in per route via
isr: true
in metadata:
live "/status", StatusLive, :index,
metadata: %{prerender: :always, isr: true}
insert_new/2
ensure only one task
regenerates a given path at a time. The lock is released automatically when the task completes.
See it in action on the /status page.
LiveView CSRF Token Swap
For prerender: :always
routes,
the plug establishes a fresh session and replaces the stale build-time CSRF token in the
prerendered HTML before serving. This allows LiveView's WebSocket to connect successfully
without infinite refresh loops.
# endpoint.ex — pass session options to enable CSRF swap
@session_options [store: :cookie, key: "_app_key", signing_salt: "..."]
plug PhoenixPrerender.Plug, session_options: @session_options
Routes with :bots_only
don't need this — bots don't run JavaScript, so there's no WebSocket connection to worry about.
<.prerendered> Component
PhoenixPrerender.Components
provides a component that freezes content at prerender time. When LiveView hydrates, the
prerendered DOM value is preserved — LiveView won't patch it.
import PhoenixPrerender.Components
# Frozen at prerender time
<.prerendered id="gen-time" tag="p" class="font-mono">
{@generated_at}
</.prerendered>
# Updates normally via WebSocket
<p>{@current_time}</p>
Uses phx-update="ignore"
under the hood. See the
/status
page for a live demo.
Distributed Regeneration
When running on multiple BEAM nodes,
:global.trans/2
ensures only
one node regenerates a given page. After regeneration, Phoenix PubSub broadcasts to all nodes
so they invalidate their local ETS caches.
If the regenerating node crashes,
:global
automatically releases the lock so another node picks up the work.
Atomic File Writes
Every file write is a two-step process: write to path.tmp,
then File.rename!/2
to the final location.
This is critical during ISR — the serving plug never reads a partially written file, even when
pages are being regenerated while being served.
Smart Serving Plug
PhoenixPrerender.Plug sits in
your endpoint before the router. The serving order is:
- 1. Manifest check — per-route prerender_mode and ISR settings
- 2. Bot detection — for :bots_only routes, check User-Agent
- 3. CSRF swap — for :always routes, inject fresh session + token
- 4. ETS cache — in-memory lookup via PageCache
- 5. Disk — check for static HTML file
- 6. Pass-through — fall to Phoenix router
Strict paths mode (default) only serves pages listed in manifest.json, preventing arbitrary file serving. Path traversal attacks are rejected.
Telemetry
Four event families for full observability:
- [:phoenix_prerender, :generate]
- [:phoenix_prerender, :render]
- [:phoenix_prerender, :serve]
- [:phoenix_prerender, :regenerate]
Manifest & Sitemap
After generation, two files are written:
- manifest.json — per-route prerender_mode, ISR flag, checksums, sizes
- sitemap.xml — standard sitemaps.org format