Skip to main content

How Rendering Works

PRISM intercepts incoming HTTP requests and decides whether to render them through a headless Chrome pipeline or proxy them directly to the origin. This page describes the full request flow from arrival to response.

Request Flow

Client Request


┌─────────────┐ no ┌────────────────┐
│ Bot Detection├────────────►│ Proxy to Origin │
└──────┬──────┘ └────────────────┘
│ yes

┌─────────────┐ no ┌────────────────┐
│Route Matching├────────────►│ Proxy to Origin │
└──────┬──────┘ └────────────────┘
│ yes

┌──────────────┐
│Render Decision│─── cache hit ──► Return cached HTML
└──────┬───────┘
│ cache miss

┌──────────────────┐
│ Acquire Chrome Tab│
└──────┬───────────┘


┌──────────────────┐
│ Navigate to │
│ origin URL │
└──────┬───────────┘


┌──────────────────┐
│ wait_for event │ (load / networkidle / selector)
└──────┬───────────┘


┌──────────────────┐
│ Auto-Scroll │ Trigger intersection observers
└──────┬───────────┘


┌──────────────────┐
│ post_wait_js │ Optional DOM fixups
└──────┬───────────┘


┌──────────────────┐
│ HTML Extraction │ document.documentElement.outerHTML
└──────┬───────────┘


┌──────────────────┐
│ Postprocessing │ lol_html transforms (strip scripts, etc.)
└──────┬───────────┘


┌──────────────────────┐
│ Content Validation │ min_html_bytes, min_text_length, require_title
└──────┬───────────────┘
│ pass

┌──────────────────┐
│ Cache & Respond │
└──────────────────┘

Step-by-Step Breakdown

1. Bot Detection

PRISM inspects the User-Agent header against a list of known search engine crawlers, social media bots, and link previewers. In bot-only mode (the default), only bot requests proceed to rendering. In render-all mode, every matching request is rendered.

2. Route Matching

The request path is matched against the configured routes.include and routes.exclude glob patterns. Static assets, API endpoints, and other non-HTML paths are excluded by default and proxied directly to the origin.

3. Render Decision

PRISM checks:

  • Is the request method GET or HEAD? (Other methods are proxied directly.)
  • Is the license valid?
  • Is the route included?
  • Is it a bot (in bot-only mode)?

Only when all conditions pass does PRISM proceed to rendering.

4. Cache Lookup

Before rendering, PRISM checks the in-memory cache. Cache keys include the normalized URL (tracking params stripped, query params sorted) and, when viewport-aware rendering is enabled, the device class suffix (::mobile or ::desktop).

  • HIT -- return cached HTML immediately.
  • STALE -- serve stale content, trigger a background re-render.
  • MISS -- proceed to render. Concurrent requests for the same URL are coalesced so only one Chrome tab is used.

5. Chrome Tab Acquisition

A tab is acquired from the Chrome tab pool. If all tabs are busy, the request queues up to pool.queue_max. If the queue is full, PRISM returns a 503 error.

6. Navigation

PRISM navigates Chrome to the origin URL with an X-Prism-Bypass header to prevent render loops. Chrome's HTTP cache is disabled to avoid 304 responses with empty bodies on reused tabs. CSP is bypassed to allow HTML extraction via document.documentElement.outerHTML.

7. Wait Strategy

PRISM waits for the page to be ready using the configured wait_for strategy:

ValueBehavior
loadWait for the load event (default)
networkidleWait until no network requests are in-flight for 500ms
CSS selectorWait until the given element appears in the DOM

8. Auto-Scroll

After the wait event fires, PRISM scrolls the page from top to bottom in 400px increments with 100ms delays. This triggers intersection observers that many SPAs use for lazy loading product grids, images, and infinite scroll content.

9. post_wait_js

If configured, PRISM executes custom JavaScript after scrolling. This is useful for DOM fixups on SPAs with lazy hydration -- for example, removing hidden classes from mega menus that React has not removed yet.

10. HTML Extraction

PRISM evaluates document.documentElement.outerHTML to capture the fully rendered DOM as an HTML string.

11. Postprocessing

The extracted HTML is processed through lol_html (Cloudflare's streaming HTML rewriter) to strip scripts, event handlers, hydration attributes, comments, and noscript tags, and to resolve lazy images. See the Postprocessing page for details.

12. Status Code Resolution

The HTTP status code is captured from Chrome's navigation response. If status_from_meta is enabled, PRISM also checks for SPA-signaled status codes via window.__PRISM_STATUS__ or <meta name="render:status_code">. See Status Codes for details.

13. Content Validation

If enabled, PRISM validates the rendered HTML against quality thresholds (min_html_bytes, min_text_length, require_title). Failures cause a fallback to the origin response without tripping the circuit breaker. See Content Validation.

14. Caching

Valid rendered HTML is stored in the in-memory cache with the configured TTL. For 5xx responses, the error_ttl_secs setting controls whether and for how long errors are cached.

15. Response

The rendered HTML is returned with appropriate headers:

  • X-Prism-Cache: HIT, MISS, STALE, or BYPASS
  • X-Prism-Render-Time: render duration in milliseconds (on cache miss)
  • X-Prism-Variant: mobile or desktop (when viewport-aware rendering is enabled)
  • Preserved origin headers: Cache-Control, Content-Language, X-Robots-Tag, Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, Link

Error Handling

When rendering fails (timeout, Chrome crash, circuit breaker open), PRISM falls back to proxying the request directly to the origin. The circuit breaker tracks consecutive failures and temporarily halts rendering attempts to avoid cascading failures.