Налаштування Matrix Fields для гнучкого контенту Craft CMS
Matrix Field — головний інструмент для page builder функціональності без сторонніх плагінів. Редактор додає блоки різних типів у довільному порядку, розробник рендерить їх через switch у Twig.
Коли використовувати Matrix
Matrix підходить для:
- Тіл сторінок зі змішаним контентом (текст + зображення + цитати + CTA)
- Секцій головної сторінки (hero + features + testimonials + pricing)
- FAQ-блоків, таймлайнів, характеристик продукту
Matrix надлишковий для:
- Простих списків однотипних елементів → використовуйте окремий Channel
- Одного типу контенту, що повторюється кілька разів → використовуйте Table field
Проектування Matrix Block Types
Приклад для поля pageBody на корпоративному сайті:
pageBody (Matrix)
├── richText
│ └── content: Redactor (formatting: all)
│
├── image
│ ├── image: Assets (single)
│ ├── caption: Plain Text
│ ├── alignment: Dropdown (left/center/right/full)
│ └── linkUrl: URL (optional)
│
├── imageText
│ ├── image: Assets (single)
│ ├── imagePosition: Lightswitch (image left/right)
│ ├── heading: Plain Text
│ ├── text: Redactor (limited)
│ └── buttonLabel + buttonUrl: Plain Text + URL
│
├── gallery
│ ├── images: Assets (multiple)
│ ├── columns: Dropdown (2/3/4)
│ └── caption: Plain Text
│
├── codeBlock
│ ├── language: Dropdown (php/js/python/bash/json/yaml)
│ └── code: Plain Text (monospace)
│
├── testimonial
│ ├── quote: Plain Text (textarea)
│ ├── author: Plain Text
│ ├── role: Plain Text
│ └── avatar: Assets (single)
│
├── stats
│ └── items (Matrix nested or Table):
│ ├── value: Plain Text
│ ├── label: Plain Text
│ └── icon: Assets
│
└── cta
├── heading: Plain Text
├── text: Plain Text
├── primaryLabel + primaryUrl: Plain Text + URL
└── secondaryLabel + secondaryUrl: Plain Text + URL (optional)
Twig-рендер Matrix
{# templates/_components/matrix-content.twig #}
{% for block in entry.pageBody.all() %}
<div class="content-block content-block--{{ block.type.handle }}" id="block-{{ block.id }}">
{% switch block.type.handle %}
{% case 'richText' %}
<div class="prose max-w-prose mx-auto">
{{ block.content }}
</div>
{% case 'image' %}
{% set img = block.image.one() %}
{% if img %}
<figure class="image-block align-{{ block.alignment }}">
{% if block.linkUrl %}
<a href="{{ block.linkUrl }}" target="_blank" rel="noopener">
{% endif %}
<img
src="{{ img.getUrl({ width: 1200 }) }}"
alt="{{ img.alt ?? '' }}"
loading="lazy"
width="{{ img.width }}"
height="{{ img.height }}">
{% if block.linkUrl %}</a>{% endif %}
{% if block.caption %}
<figcaption>{{ block.caption }}</figcaption>
{% endif %}
</figure>
{% endif %}
{% case 'imageText' %}
<section class="image-text {{ block.imagePosition ? 'image-right' : 'image-left' }}">
<div class="image-text__image">
{% set img = block.image.one() %}
{% if img %}
<img src="{{ img.getUrl({ width: 600 }) }}" alt="{{ img.alt ?? '' }}" loading="lazy">
{% endif %}
</div>
<div class="image-text__content">
{% if block.heading %}<h2>{{ block.heading }}</h2>{% endif %}
<div class="prose">{{ block.text }}</div>
{% if block.buttonLabel and block.buttonUrl %}
<a href="{{ block.buttonUrl }}" class="btn">{{ block.buttonLabel }}</a>
{% endif %}
</div>
</section>
{% case 'codeBlock' %}
<pre class="code-block" data-language="{{ block.language }}">
<code class="language-{{ block.language }}">{{ block.code | escape }}</code>
</pre>
{% case 'testimonial' %}
<blockquote class="testimonial">
<p>{{ block.quote }}</p>
<footer>
{% set avatar = block.avatar.one() %}
{% if avatar %}
<img src="{{ avatar.getUrl({ width: 80, height: 80, mode: 'crop' }) }}" alt="{{ block.author }}">
{% endif %}
<cite>
<strong>{{ block.author }}</strong>
{% if block.role %}<span>{{ block.role }}</span>{% endif %}
</cite>
</footer>
</blockquote>
{% case 'cta' %}
<div class="cta-block">
{% if block.heading %}<h2>{{ block.heading }}</h2>{% endif %}
{% if block.text %}<p>{{ block.text }}</p>{% endif %}
<div class="cta-block__buttons">
{% if block.primaryLabel %}
<a href="{{ block.primaryUrl }}" class="btn btn--primary">{{ block.primaryLabel }}</a>
{% endif %}
{% if block.secondaryLabel %}
<a href="{{ block.secondaryUrl }}" class="btn btn--secondary">{{ block.secondaryLabel }}</a>
{% endif %}
</div>
</div>
{% endswitch %}
</div>
{% endfor %}
Продуктивність: Eager Loading
Matrix-блоки з Assets та Relations створюють N+1 запити без eager loading:
{# Погано — N+1 для кожного блоку з image #}
{% for block in entry.pageBody.all() %}
{# Добре — preload всіх зв'язків #}
{% set blocks = entry.pageBody
.with(['image', 'images', 'avatar'])
.all() %}
Обмеження типів блоків через Entry Type
Різні Entry Types можуть використовувати різні набори блоків Matrix, але саме поле Matrix — спільне. Це реалізується через JavaScript у CP (умовна видимість), але не на рівні даних.
Налаштування Matrix з 5–8 типами блоків та шаблонами займає 2–4 дні.







