Skip to content

Rendering Blocks

This guide explains how Basker renders content blocks and the lifecycle of block rendering in your templates.

When you use the {% stageblocks %} tag in a template, Basker:

  1. Retrieves the blocks array from the content object
  2. Iterates through each block
  3. Looks up the corresponding block template
  4. Renders each block with its data
  5. Wraps the output in a container div
{# In your template #}
{% stageblocks page %}
{# Outputs #}
<div class="content-blocks">
<!-- Rendered block 1 -->
<!-- Rendered block 2 -->
<!-- ... -->
</div>

For each block, the renderer determines which template file to use:

  1. Check for override - If a template override was specified in the stageblocks tag
  2. Use default path - Look for blocks/[blockType].liquid
{# Default resolution #}
{% stageblocks page %}
{# Block type "hero" → blocks/hero.liquid #}
{# Block type "feature-panel" → blocks/feature-panel.liquid #}
{# With override #}
{% stageblocks page, hero:blocks/hero-large.liquid %}
{# Block type "hero" → blocks/hero-large.liquid #}
{# Block type "feature-panel" → blocks/feature-panel.liquid (default) #}

Each block template receives a block object containing:

PropertyTypeDescription
block.idstringUnique identifier for this block instance
block.blockTypestringThe block type name
block.[field]anyValues for each field defined in the schema
block.[field]_htmlstringHTML output for richtext fields

The block also has access to the parent template’s context, including global objects like settings, navigation, and content-specific objects.

{# blocks/hero.liquid #}
<section id="{{ block.id }}" class="hero">
<h1>{{ block.heading }}</h1>
{# Access parent context #}
{% if settings.header.show_cta %}
<a href="{{ settings.header.cta_link }}">Learn More</a>
{% endif %}
</section>

The liquidBlock type allows editors to write custom Liquid code directly in the CMS. The code is parsed and rendered inline:

<section id="[block-id]" class="element liquid-block">
<div class="liquid-block__inner">
<!-- Rendered liquid code -->
</div>
</section>

The reusableContentBlock type references a shared content entry that contains its own blocks. These are rendered recursively:

<section id="[block-id]" class="element reusable-content-block">
<div class="reusable-content-block__inner">
<!-- Recursively rendered blocks from reusable content -->
</div>
</section>

Reusable content blocks have a maximum recursion depth of 10 to prevent infinite loops.

┌─────────────────────────────────────────────────────────┐
│ {% stageblocks page %} │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Get blocks array from page.blocks │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ For each block: │
│ │
│ 1. Hydrate relationship data (navigation, media) │
│ 2. Determine block type │
│ 3. Find template (override or blocks/[type].liquid) │
│ 4. Render template with block context │
│ 5. Append output │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Wrap all output in <div class="content-blocks"> │
└─────────────────────────────────────────────────────────┘

Before rendering, block data is “hydrated” - relationship fields are resolved to include their full data. This means:

  • Media fields include all image properties (url, alt, dimensions, focal point)
  • Page relationships include navigation data (title, relativePath, children)
  • Content relationships include the linked content’s properties
{# The image field is automatically hydrated #}
{% if block.image %}
<img
src="{{ block.image | image_url: width: 800 }}"
alt="{{ block.image.alt }}"
width="{{ block.image.width }}"
height="{{ block.image.height }}"
>
{% endif %}
{# Page relationships include navigation data #}
{% if block.linked_page %}
<a href="{{ block.linked_page.relativePath }}">
{{ block.linked_page.title }}
</a>
{% endif %}

When a block fails to render, Basker outputs an HTML comment instead of breaking the page:

<!-- Error rendering block hero: Template not found -->

This allows other blocks to continue rendering even if one fails.

  1. Block count - Rendering many blocks increases page generation time
  2. Relationship depth - Deep relationship chains require more database queries
  3. Reusable content - Each reusable content block adds a database lookup
  4. Liquid blocks - Custom Liquid code is parsed on every request

To debug block rendering issues:

  1. Check block type - Ensure the blockType matches your template filename
  2. Verify template exists - The file must be at blocks/[blockType].liquid
  3. Inspect block data - Output {{ block | json }} to see available data
  4. Check for errors - Look for HTML comments in the output indicating render failures
{# Debug: output all block data #}
<pre>{{ block | json }}</pre>
{# Debug: check block type #}
<p>Block type: {{ block.blockType }}</p>