Розробка кастомних шаблонів Craft CMS (Twig)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомних шаблонів Craft CMS (Twig)
Середня
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка кастомних шаблонів Craft CMS (Twig)

Craft CMS використовує Twig 3 з додатковими фільтрами, функціями та глобальними змінними. Шаблони розв'язуються за URI: запит /blog/my-post шукає шаблони templates/blog/my-post.twig, templates/blog/_entry.twig, templates/blog.twig по порядку.

Структура шаблонів

templates/
├── _layouts/
│   ├── base.twig         # базовий HTML-скелет
│   ├── default.twig      # стандартний layout
│   └── fullwidth.twig    # без сайдбара
├── _components/
│   ├── header.twig
│   ├── footer.twig
│   ├── post-card.twig
│   └── breadcrumbs.twig
├── _macros/
│   └── helpers.twig      # повторювані Twig-функції
├── index.twig            # головна сторінка
├── 404.twig              # сторінка помилки
├── blog/
│   ├── index.twig        # список постів
│   └── _entry.twig       # одиничний пост
└── services/
    ├── index.twig
    └── _entry.twig

Базовий layout

{# templates/_layouts/base.twig #}
<!DOCTYPE html>
<html lang="{{ craft.app.language }}">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{% block title %}{{ siteName }}{% endblock %}</title>
  {% block meta %}{% endblock %}
  {{ craft.vite.stylesheet('src/css/app.pcss') }}
</head>
<body class="{% block bodyClass %}{% endblock %}">
  {% include '_components/header' %}

  <main id="main" tabindex="-1">
    {% block content %}{% endblock %}
  </main>

  {% include '_components/footer' %}

  {{ craft.vite.script('src/js/app.ts') }}
  {% block scripts %}{% endblock %}
</body>
</html>

Шаблон списку записів

{# templates/blog/index.twig #}
{% extends '_layouts/default' %}

{% set pageInfo = craft.app.request.getQueryParam('page') %}

{% block content %}
  {% paginate craft.entries()
    .section('blog')
    .status('live')
    .orderBy('postDate desc')
    .limit(12) as pageInfo, posts %}

  <div class="blog-grid">
    {% for post in posts %}
      {% include '_components/post-card' with { post: post } only %}
    {% else %}
      <p>Публікацій поки нема</p>
    {% endfor %}
  </div>

  {% if pageInfo.totalPages > 1 %}
    <nav class="pagination">
      {% if pageInfo.prevUrl %}
        <a href="{{ pageInfo.prevUrl }}" rel="prev">← Назад</a>
      {% endif %}

      {% for page, url in pageInfo.getPrevUrls(3) %}
        <a href="{{ url }}">{{ page }}</a>
      {% endfor %}

      <span class="current">{{ pageInfo.currentPage }}</span>

      {% for page, url in pageInfo.getNextUrls(3) %}
        <a href="{{ url }}">{{ page }}</a>
      {% endfor %}

      {% if pageInfo.nextUrl %}
        <a href="{{ pageInfo.nextUrl }}" rel="next">Вперед →</a>
      {% endif %}
    </nav>
  {% endif %}
{% endblock %}

Шаблон записи з Matrix

{# templates/blog/_entry.twig #}
{% extends '_layouts/default' %}
{% set post = entry %}

{% block title %}{{ post.title }} | {{ siteName }}{% endblock %}

{% block meta %}
  <meta name="description" content="{{ post.seoDescription ?? post.summary | striptags | slice(0, 160) }}">
  <meta property="og:title" content="{{ post.title }}">
  <meta property="og:image" content="{{ post.heroImage.one().getUrl('ogImage') ?? '' }}">
{% endblock %}

{% block content %}
<article class="post">
  <header>
    {% set heroImage = post.heroImage.one() %}
    {% if heroImage %}
      <picture>
        <source
          srcset="{{ heroImage.getUrl({ width: 800, format: 'webp' }) }} 800w,
                  {{ heroImage.getUrl({ width: 1600, format: 'webp' }) }} 1600w"
          type="image/webp">
        <img
          src="{{ heroImage.getUrl('large') }}"
          alt="{{ heroImage.alt ?? post.title }}"
          width="{{ heroImage.width }}"
          height="{{ heroImage.height }}"
          fetchpriority="high">
      </picture>
    {% endif %}

    <div class="post-meta">
      {% for category in post.categories.all() %}
        <a href="{{ category.url }}" class="category">{{ category.title }}</a>
      {% endfor %}
    </div>

    <h1>{{ post.title }}</h1>

    <div class="byline">
      {% set author = post.author %}
      {% if author.photo.one() %}
        <img src="{{ author.photo.one().getUrl('avatar') }}" alt="{{ author.fullName }}">
      {% endif %}
      <span>{{ author.fullName }}</span>
      <time datetime="{{ post.postDate | date('Y-m-d') }}">
        {{ post.postDate | date('d F Y') }}
      </time>
    </div>
  </header>

  <div class="post-body">
    {% for block in post.pageContent.all() %}
      {% switch block.type %}
        {% case 'richText' %}
          <div class="prose">{{ block.body }}</div>

        {% case 'pullQuote' %}
          <blockquote class="pull-quote">
            <p>{{ block.quote }}</p>
            {% if block.attribution %}<cite>{{ block.attribution }}</cite>{% endif %}
          </blockquote>

        {% case 'imageBlock' %}
          {% set img = block.image.one() %}
          {% if img %}
            <figure class="image-block {{ block.size }}">
              <img src="{{ img.getUrl({ width: 1200 }) }}" alt="{{ img.alt }}">
              {% if block.caption %}<figcaption>{{ block.caption }}</figcaption>{% endif %}
            </figure>
          {% endif %}

        {% case 'codeSnippet' %}
          <pre><code class="language-{{ block.language }}">{{ block.code | escape }}</code></pre>
      {% endswitch %}
    {% endfor %}
  </div>

  {# Пов'язані пости через відносини #}
  {% set related = craft.entries()
    .section('blog')
    .relatedTo(post.tags.all())
    .id('not ' ~ post.id)
    .limit(3)
    .all() %}

  {% if related | length %}
    <aside class="related">
      <h3>Читайте також</h3>
      {% for item in related %}
        {% include '_components/post-card' with { post: item } only %}
      {% endfor %}
    </aside>
  {% endif %}
</article>
{% endblock %}

Макроси та розширення Twig

{# templates/_macros/helpers.twig #}
{% macro truncate(text, length = 150) %}
  {% if text | length > length %}
    {{ text | slice(0, length) }}…
  {% else %}
    {{ text }}
  {% endif %}
{% endmacro %}

{% macro breadcrumbs(entry) %}
  <nav aria-label="Хлібні крошки">
    <a href="/">Головна</a>
    {% for ancestor in entry.getAncestors() %}
      <a href="{{ ancestor.url }}">{{ ancestor.title }}</a>
    {% endfor %}
    <span aria-current="page">{{ entry.title }}</span>
  </nav>
{% endmacro %}

Named Image Transforms

// У Craft CP: Settings → Assets → Image Transforms
// Або через конфіг:

// config/image-transforms.php (Craft 4+)
return [
  'thumbnail' => ['width' => 400, 'height' => 300, 'mode' => 'crop'],
  'large'     => ['width' => 1200, 'height' => 630, 'mode' => 'crop'],
  'avatar'    => ['width' => 100, 'height' => 100, 'mode' => 'crop', 'position' => 'center-center'],
  'ogImage'   => ['width' => 1200, 'height' => 630, 'mode' => 'crop', 'format' => 'jpg'],
];

Розробка шаблонів для корпоративного сайту (головна, послуги, блог, контакти) займає 4–8 днів залежно від складності дизайну.