RenderPlan Middleware

Inspect rendering decisions from middleware for analytics, caching, and debugging

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

What Is a RenderPlan?

A RenderPlan is Chirp's record of how it decided to render a request: full page vs. fragment, which template block, whether to wrap it in a layout, and which OOB regions to swap. Chirp builds this plan internally for every page response, then stashes it on the request so your middleware can read it after the fact.

Reach for it when you want to act on the rendering decision -- log it, collect metrics on it, or cache by it -- without re-deriving how the request was served. The fields you usually touch are:

  • intent --"full_page", "page_fragment", or "local_fragment"
  • apply_layouts -- whether content was wrapped in the layout chain
  • region_updates -- OOB shell region swaps (breadcrumbs, sidebar, etc.)
  • layout_start_index -- how deep into the layout chain rendering started

Reading the Plan

After content negotiation runs, theRenderPlan is stashed on the request. Read it with get_render_plan():

from chirp import AnyResponse, Next, Request, get_render_plan

async def my_middleware(request: Request, next: Next) -> AnyResponse:
    response = await next(request)
    plan = get_render_plan(request)
    if plan is not None:
        print(plan.intent)           # "full_page" or "page_fragment"
        print(plan.apply_layouts)    # True for full pages
        print(plan.layout_start_index)  # Layout chain depth
    return response

Returns None for non-page responses (strings, dicts, Fragment, EventStream, etc.).

Practical Patterns

Render Analytics

Track which rendering paths are hit most:

from chirp import get_render_plan

async def analytics_middleware(request: Request, next: Next) -> AnyResponse:
    response = await next(request)
    plan = get_render_plan(request)
    if plan is not None:
        metrics.increment(f"render.{plan.intent}")
        if plan.region_updates:
            metrics.increment("render.with_oob_regions")
    return response

Plan-Aware Caching

Cache by render plan characteristics, not just URL:

from chirp import get_render_plan

async def cache_middleware(request: Request, next: Next) -> AnyResponse:
    plan_key = _cache_key(request)
    cached = cache.get(plan_key)
    if cached is not None:
        return cached

    response = await next(request)
    plan = get_render_plan(request)

    # Only cache full-page renders (fragments are user-specific)
    if plan is not None and plan.intent == "full_page":
        cache.set(plan_key, response, ttl=60)
    return response

Debug Logging

Log rendering decisions in development:

from chirp import get_render_plan

async def debug_middleware(request: Request, next: Next) -> AnyResponse:
    response = await next(request)
    plan = get_render_plan(request)
    if plan is not None:
        logger.debug(
            "Rendered %s: intent=%s layouts=%s regions=%d",
            request.path,
            plan.intent,
            plan.apply_layouts,
            len(plan.region_updates),
        )
    return response