Skip to content

Configuration Reference

PRISM is configured via a TOML file (default: /etc/prism/config.toml).

Server

[server]
address = "0.0.0.0:4000"       # Listen address
origin = "http://localhost:3000" # Your SPA backend
mode = "bot-only"                # "bot-only" or "render-all"
shadow = false                   # Shadow mode: render in background, serve original
drain_timeout_secs = 30          # Graceful shutdown timeout
Option Type Default Description
address string "0.0.0.0:4000" Address PRISM listens on
origin string "http://localhost:3000" Your SPA origin URL
mode string "bot-only" "bot-only": render only for crawlers. "render-all": render for everyone
shadow bool false Render in background and log diffs without serving rendered HTML
drain_timeout_secs integer 30 Seconds to wait for in-flight renders during shutdown

Mode: bot-only vs render-all

bot-only (default, recommended): Only search engine crawlers and social bots get rendered HTML. Regular users get the normal SPA response. Zero performance impact for human visitors.

render-all: Every request gets rendered HTML. Useful when you want server-side rendering without modifying your SPA code. Higher resource usage — every page view goes through Chrome.

Shadow Mode

When shadow = true, PRISM renders pages in the background but serves the original (un-rendered) response. It logs any differences between the rendered and original HTML. Useful for testing PRISM before going live.

Render

[render]
wait_for = "load"                 # When to consider the page "done"
timeout_secs = 10                 # Max render time per page
block_resources = [               # Resource types to skip (faster renders)
    "font", "image", "media", "stylesheet"
]
Option Type Default Description
wait_for string "load" Page readiness signal (see below)
timeout_secs integer 10 Maximum seconds per render
block_resources array ["font", "image", "media", "stylesheet"] Chrome resource types to block

wait_for Options

Value Description Speed
"load" Wait for window.onload event Fast, good default
"domcontentloaded" Wait for DOM ready (before images) Fastest
"networkidle" Wait until no network activity for 500ms Slowest, most complete
"selector:css" Wait for a CSS selector to appear, e.g., "selector:.product-loaded" Custom

Blocking resources

Blocking fonts, images, and stylesheets during rendering is safe — crawlers don't need them. This reduces render time by 30-50% and saves bandwidth.

Chrome Pool

[render.pool]
tabs = 8                          # Number of Chrome tabs (parallel renders)
max_renders_per_tab = 50          # Recycle tab after N renders (prevents memory leaks)
queue_max = 100                   # Max queued render requests
Option Type Default Description
tabs integer 8 Number of concurrent Chrome tabs
max_renders_per_tab integer 50 Recycle tab after this many renders
queue_max integer 100 Maximum queued requests (rejects above this)

Sizing the Pool

Server RAM Recommended Tabs Concurrent Renders
1 GB 2-4 2-4
2 GB 4-8 4-8
4 GB 8-16 8-16
8 GB+ 16-32 16-32

Each Chrome tab uses approximately 50-150 MB of RAM depending on page complexity.

Circuit Breaker

[render.circuit_breaker]
failure_threshold = 5             # Consecutive failures before opening circuit
recovery_timeout_secs = 30        # Seconds before trying again
half_open_max_requests = 1        # Test requests in half-open state

When Chrome fails repeatedly (crashes, timeouts), the circuit breaker opens and stops sending requests to Chrome. After recovery_timeout_secs, it enters half-open state and tests with a single request. If that succeeds, the circuit closes and normal operation resumes.

Cache

[cache]
enabled = true
max_entries = 10000               # Maximum cached pages
max_memory_bytes = 268435456      # 256 MiB memory limit
default_ttl_secs = 3600           # 1 hour default TTL
grace_period_secs = 300           # 5 minutes stale serving
respect_cache_control = false     # Respect origin Cache-Control headers
Option Type Default Description
enabled bool true Enable in-memory cache
max_entries integer 10000 Maximum number of cached pages
max_memory_bytes integer 268435456 Memory limit (256 MiB)
default_ttl_secs integer 3600 Default time-to-live (1 hour)
grace_period_secs integer 300 Serve stale content while re-rendering
respect_cache_control bool false Honor origin's Cache-Control headers

Request Coalescing

When multiple requests arrive for the same URL simultaneously, PRISM renders it once and serves the result to all waiting requests. This prevents "thundering herd" problems where 100 bot requests for the same page would trigger 100 Chrome renders.

SPA Detection

[detect]
auto = false                      # Auto-detect if a page is an SPA
max_body_text_bytes = 500         # If body text < 500 bytes, probably an SPA
min_script_tags = 2               # If >= 2 script tags, probably an SPA
mount_points = [                  # Common SPA mount point IDs
    "app", "root", "__next", "__nuxt"
]
header = "X-Prism-Render"        # Origin can request rendering via this header

When auto = true, PRISM fetches the page first, inspects the HTML, and decides whether it needs rendering. Useful when PRISM sits in front of a mixed site (some pages are SPAs, some are server-rendered).

Routes

[routes]
include = ["/**"]                 # Glob patterns to render
exclude = [                       # Glob patterns to skip
    "/api/**",
    "/graphql",
    "**/*.js", "**/*.css", "**/*.json",
    "**/*.png", "**/*.jpg", "**/*.gif", "**/*.svg", "**/*.ico",
    "**/*.woff", "**/*.woff2", "**/*.ttf",
    "/_next/**", "/static/**", "/admin/**",
]

Routes use glob patterns. A request must match an include pattern AND not match any exclude pattern to be rendered.

Always exclude static assets

CSS, JS, images, and fonts should never go through Chrome. The default excludes cover most cases. Add any custom static paths your app uses.

Bot Detection

[bot]
patterns = [
    "Googlebot", "Bingbot", "Yandex", "Baiduspider",
    "DuckDuckBot", "Slurp", "facebookexternalhit",
    "LinkedInBot", "Twitterbot", "Applebot",
    "GPTBot", "ClaudeBot", "ChatGPT-User",
    "AhrefsBot", "SemrushBot", "MJ12bot",
    # ... 70+ patterns included by default
]

Bot detection uses case-insensitive substring matching on the User-Agent header. The default list includes all major search engines, social crawlers, AI bots, and SEO tools.

Admin API

[admin]
enabled = true
address = "127.0.0.1:4001"       # Admin listens on separate port
# bearer_token = "your-secret-token"
Endpoint Method Description
/health GET Health check (returns 200 if healthy)
/status GET Uptime, cache stats, pool stats, render counts
/metrics GET Prometheus format metrics
/purge/url POST Purge a single cached URL
/purge/pattern POST Purge by glob pattern
/purge/all POST Flush entire cache
/render POST Manually trigger a render

Protect the admin API

Always set bearer_token in production. The admin API can purge your cache and trigger renders.

Security

[security]
allowed_origins = []              # Empty = allow all origins
block_private_cidrs = true        # Block SSRF to private networks
rate_limit_per_domain = 10        # Max concurrent renders per domain
Option Type Default Description
allowed_origins array [] Restrict which origins PRISM can fetch from
block_private_cidrs bool true Block renders to private IP ranges (SSRF protection)
rate_limit_per_domain integer 10 Max concurrent renders per domain

Keep block_private_cidrs enabled

Disabling SSRF protection allows Chrome to access internal services, databases, and cloud metadata endpoints. Only disable for testing.

Logging

[logging]
level = "info"                    # debug, info, warn, error
format = "pretty"                 # "pretty" (human) or "json" (structured)

Complete Example

[server]
address = "0.0.0.0:4000"
origin = "https://my-react-app.com"
mode = "bot-only"

[render]
wait_for = "networkidle"
timeout_secs = 15
block_resources = ["font", "image", "media", "stylesheet"]

[render.pool]
tabs = 8
max_renders_per_tab = 50

[cache]
enabled = true
max_entries = 50000
max_memory_bytes = 536870912    # 512 MiB
default_ttl_secs = 7200        # 2 hours
grace_period_secs = 600        # 10 minutes

[routes]
include = ["/**"]
exclude = ["/api/**", "/graphql", "**/*.js", "**/*.css", "**/*.json",
           "**/*.png", "**/*.jpg", "**/*.svg", "**/*.ico",
           "**/*.woff2", "/_next/**", "/static/**", "/admin/**"]

[admin]
enabled = true
address = "127.0.0.1:4001"
bearer_token = "your-secret-token-here"

[security]
block_private_cidrs = true
rate_limit_per_domain = 10

[logging]
level = "info"
format = "json"