# Block Caching URL: /docs/advanced/block-caching/ Section: advanced Tags: advanced, caching, performance -------------------------------------------------------------------------------- Block Caching Kida's static analysis can determine which template blocks are safe to cache and at what scope. This article shows how to connect analysis results to runtime caching using CachedBlocksDict — the same mechanism Bengal uses for 40-60% faster incremental builds. The Pipeline analyze → identify cacheable blocks → render once → cache → reuse Analyze templates with block_metadata() or template_metadata() Identify site-scoped blocks (cache_scope == "site") Render those blocks once Cache the HTML output Inject cached HTML via CachedBlocksDict for subsequent renders Step 1: Identify Cacheable Blocks Use the analysis API to find blocks that can be cached: from kida import Environment, FileSystemLoader env = Environment( loader=FileSystemLoader("templates/"), preserve_ast=True, # Required for analysis ) template = env.get_template("page.html") # Get metadata for all blocks meta = template.template_metadata() if meta: # Blocks safe to cache at the site level site_blocks = meta.site_cacheable_blocks() for block in site_blocks: print(f"{block.name}: scope={block.cache_scope}, pure={block.is_pure}") # nav: scope=site, pure=pure # footer: scope=site, pure=pure Cache Scope Meaning Scope Meaning Cache Strategy "site" Same output for every page Cache once per build, reuse everywhere "page" Varies per page Cache per page, invalidate on page change "none" Impure block Do not cache "unknown" Cannot determine Treat as uncacheable Step 2: Render and Cache Site Blocks Render the first page to get site-scoped block HTML, then cache it: # Render one page to populate site-scoped blocks first_page_html = template.render(page=pages[0], site=site) # Extract site-scoped block HTML by rendering blocks individually site_cache = {} for block in site_blocks: # render_block renders a single block in isolation block_html = template.render_block(block.name, page=pages[0], site=site) site_cache[block.name] = block_html Step 3: Inject Cached Blocks Use CachedBlocksDict to intercept block lookups and return cached HTML: from kida.template.cached_blocks import CachedBlocksDict cached_names = frozenset(site_cache.keys()) stats = {"hits": 0, "misses": 0} # Render remaining pages with cached site blocks for page in pages[1:]: # CachedBlocksDict wraps the normal blocks dict cached_blocks = CachedBlocksDict( original=None, # Template will populate via setdefault() cached=site_cache, # Pre-rendered HTML cached_names=cached_names, stats=stats, # Optional hit/miss tracking ) html = template.render( page=page, site=site, _blocks=cached_blocks, # Inject cached blocks ) CachedBlocksDict CachedBlocksDict is a dict-like wrapper that intercepts block lookups: from kida.template.cached_blocks import CachedBlocksDict wrapper = CachedBlocksDict( original=None, # Underlying blocks dict cached={"nav": "<nav>...</nav>"}, # Pre-rendered HTML cached_names=frozenset({"nav"}), # Which blocks are cached stats={"hits": 0, "misses": 0}, # Optional statistics ) When the template calls blocks.get("nav"), the wrapper returns a function that produces the cached HTML instead of re-rendering. For non-cached blocks, it falls through to the original dict. Supported Operations Method Behavior .get(key) Returns cached wrapper or falls through .setdefault(key, default) Cached blocks take precedence [key] Returns cached wrapper or falls through [key] = value Writes to original dict key in dict Checks both cached and original .keys() Union of cached and original keys .copy() Returns plain dict with cached wrappers Cache Statistics Pass a shared stats dict to track hit/miss rates: stats = {"hits": 0, "misses": 0} for page in pages: blocks = CachedBlocksDict(None, cache, cached_names, stats=stats) template.render(page=page, site=site, _blocks=blocks) hit_rate = stats["hits"] / (stats["hits"] + stats["misses"]) print(f"Block cache hit rate: {hit_rate:.1%}") Complete Example from kida import Environment, FileSystemLoader from kida.template.cached_blocks import CachedBlocksDict env = Environment( loader=FileSystemLoader("templates/"), preserve_ast=True, ) template = env.get_template("page.html") # 1. Analyze meta = template.template_metadata() site_blocks = meta.site_cacheable_blocks() if meta else [] site_block_names = frozenset(b.name for b in site_blocks) # 2. Render first page (populates cache) first_html = template.render(page=pages[0], site=site) site_cache = {} for block in site_blocks: site_cache[block.name] = template.render_block( block.name, page=pages[0], site=site ) # 3. Render remaining pages with cache stats = {"hits": 0, "misses": 0} results = [first_html] for page in pages[1:]: blocks = CachedBlocksDict(None, site_cache, site_block_names, stats=stats) results.append(template.render(page=page, site=site, _blocks=blocks)) print(f"Rendered {len(results)} pages") print(f"Cache hits: {stats['hits']}, misses: {stats['misses']}") When to Use Block Caching Scenario Benefit Static site generation 40-60% faster full-site rebuilds Server-side rendering with shared layout Avoid re-rendering nav/footer per request Multi-page PDF generation Cache header/footer across pages Email batch rendering Cache shared template chrome Block caching is most effective when: Templates use inheritance with shared blocks (nav, footer, sidebar) The same template renders many times with different page data Site-wide data (menus, config) doesn't change between renders See Also Static Analysis — Block-level dependency and purity analysis Profiling — Identify slow blocks worth caching Fragment Caching — Template-level {% cache %} syntax Performance — Overall optimization strategy -------------------------------------------------------------------------------- Metadata: - Word Count: 707 - Reading Time: 4 minutes