Troubleshooting
Rendered page shows a loading spinner
The page is not fully loaded before PRISM captures the HTML.
Solutions:
- Increase
render.wait_for.timeout_ms(default is likely too short for your SPA). - Switch
render.wait_for.strategyfrom"networkidle"to"selector"and specify a CSS selector that appears only after the page is fully rendered:[render.wait_for]
strategy = "selector"
selector = "#main-content"
timeout_ms = 10000 - If the page uses lazy loading, the content may never appear without scrolling. Use
post_wait_jsto trigger scroll or force-load the content.
Empty page or content validation failure
PRISM's content validation rejected the rendered HTML because it was too small.
Solutions:
- Check the
render.content_validation.min_html_bytessetting. If your pages are legitimately small, lower the threshold:[render.content_validation]
min_html_bytes = 512 - For testing, you can temporarily disable content validation:
[render.content_validation]
enabled = false - Check
prism_content_validation_failures_totalmetric to see how often this occurs. - Use
POST /renderon the admin API to test a specific URL and inspect the result.
Chrome won't start
PRISM cannot launch the headless Chrome process.
Solutions:
- Verify Chrome or Chromium is installed and in your PATH:
which google-chrome || which chromium-browser || which chromium - If running in Docker or as a non-root user, ensure the Chrome sandbox is disabled:
[render.chrome]
args = ["--no-sandbox", "--disable-setuid-sandbox"] - Check file permissions on the Chrome binary.
- Ensure sufficient memory is available. Chrome requires at least 256 MB of free memory to start.
- Check logs for the specific Chrome launch error message.
304 empty responses
Chrome's internal cache returns a 304 Not Modified with no body when fetching the origin page, resulting in empty HTML being cached.
Fix: This was fixed in PRISM v1.0.0 by calling SetCacheDisabled on every new Chrome tab. If you are seeing this on v1.0.0+, check that your origin is not returning 304 responses to PRISM's proxy requests. PRISM strips conditional request headers (If-None-Match, If-Modified-Since) to prevent this, but a misconfigured origin or CDN in front of the origin could still cause it.
Render loop
PRISM renders a page, which causes Chrome to fetch the page through PRISM, which triggers another render, creating an infinite loop.
Solutions:
- PRISM uses an internal
X-Prism-Bypassheader with a random token to break the loop. This works automatically when Chrome connects through localhost. - If PRISM sits behind a reverse proxy (Nginx, Apache), the proxy must strip the
X-Prism-Bypassheader from external requests to prevent bypass forgery:# Nginx
proxy_set_header X-Prism-Bypass "";# Apache
RequestHeader unset X-Prism-Bypass - If Chrome is configured to access the origin through an external URL that routes through the reverse proxy, ensure the bypass header is preserved on the loopback path.
High memory usage
The PRISM process or Chrome is consuming excessive memory.
Solutions:
- Set a cache memory limit:
[cache]
max_memory_bytes = 536870912 # 512 MB - Reduce the number of renders per tab before recycling (prevents Chrome tab memory leaks):
[render.pool]
max_renders_per_tab = 50 - Reduce the tab pool size:
[render.pool]
tabs = 4 - Monitor
process_resident_memory_bytesandprism_cache_memory_bytesmetrics to understand where memory is going. - Chrome RSS grows over time even with tab recycling. If
prism_chrome_restarts_totalis zero and memory keeps growing, consider setting a lowermax_renders_per_tab.
Rate limiting (429 responses)
Requests are being rejected with HTTP 429 Too Many Requests.
Solutions:
- Check per-IP rate limit configuration:
[server]
rate_limit_per_ip = 60 # requests per minute per IP - Check per-domain render rate limits:
[render]
rate_limit_per_domain = 100 - Monitor
prism_rate_limit_rejections_totalto see rejection frequency. - If legitimate traffic is being rate-limited, increase the limits or ensure your load balancer distributes traffic across multiple PRISM instances.
SSRF blocked
Requests to certain origins are being rejected by the security filter.
Solutions:
- Check the allowed origins configuration:
[security]
allowed_origins = ["https://example.com", "https://staging.example.com"] - By default, PRISM blocks requests to private/internal CIDRs (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8) to prevent SSRF attacks:[security]
block_private_cidrs = true # default - If your origin is on a private network (e.g., Docker internal networking), you may need to disable this check and use
allowed_originsas your security boundary instead.
Venia mega menu not visible
When rendering PWA Studio (Venia) storefronts, the mega menu is present in the DOM but hidden via CSS.
Solution: Use post_wait_js to remove the hidden class after rendering completes:
[render]
post_wait_js = """
document.querySelectorAll('.megaMenu-root_hidden').forEach(el => {
el.classList.remove('megaMenu-root_hidden');
});
"""
Hydration flash in render-all mode
When using mode = "render-all", human visitors may briefly see the server-rendered HTML before the JavaScript framework hydrates and takes over. This can cause a visual "flash" where content shifts or changes.
This is expected behavior when strip_scripts = false (the default). The rendered HTML and the hydrated DOM may differ slightly because:
- PRISM renders an anonymous version of the page (no cookies/auth forwarded to Chrome).
- The client-side framework re-renders with the actual user's session state.
Mitigations:
- Use
mode = "bot-only"(the default) to only render for bots, avoiding the hydration flash for human visitors entirely. - If you must use
render-all, enablestrip_scripts = trueto prevent hydration, but be aware this disables all client-side interactivity.