Разработка кастомной темы Ghost (Handlebars)
Ghost темы используют Handlebars — логически упрощённый шаблонизатор. В отличие от PHP-шаблонов WordPress, в Handlebars нет произвольного кода — только хелперы Ghost и встроенные выражения. Это ограничение обеспечивает безопасность, но требует понимания системы хелперов.
Структура темы и обязательные файлы
my-theme/
├── package.json # обязательно: name, version, engines.ghost
├── index.hbs # главная страница / список постов
├── post.hbs # шаблон поста
├── page.hbs # статические страницы
├── error.hbs # страницы ошибок (404, 500)
├── tag.hbs # архив по тегу (опционально, fallback на index)
├── author.hbs # страница автора (опционально)
├── partials/ # переиспользуемые части
│ ├── header.hbs
│ ├── footer.hbs
│ └── post-card.hbs
└── assets/
├── css/screen.css # основной CSS
└── js/main.js
// package.json
{
"name": "my-theme",
"description": "Custom Ghost theme",
"version": "1.0.0",
"engines": { "ghost": ">=5.0.0", "ghost-api": "v5" },
"license": "MIT",
"config": {
"posts_per_page": 12,
"image_sizes": {
"xs": { "width": 300 },
"s": { "width": 600 },
"m": { "width": 1200 },
"l": { "width": 2000 }
},
"card_assets": true,
"custom": {
"header_style": {
"type": "select",
"options": ["Center", "Left", "Right"],
"default": "Center"
},
"show_reading_time": {
"type": "boolean",
"default": true
}
}
}
}
Основные хелперы и контекст
{{! index.hbs — список постов}}
{{#foreach posts}}
<article class="post-card {{post_class}}">
{{#if feature_image}}
<figure>
<img srcset="{{img_url feature_image size="s"}} 300w,
{{img_url feature_image size="m"}} 600w,
{{img_url feature_image size="l"}} 1000w"
sizes="(max-width: 768px) 100vw, 50vw"
src="{{img_url feature_image size="m"}}"
alt="{{title}}">
</figure>
{{/if}}
<div class="post-card-content">
<header>
{{#primary_tag}}
<a href="{{url}}" class="post-tag">{{name}}</a>
{{/primary_tag}}
<h2><a href="{{url}}">{{title}}</a></h2>
</header>
{{#if excerpt}}
<p>{{excerpt words="30"}}</p>
{{/if}}
<footer>
{{#primary_author}}
<img src="{{img_url profile_image size="xs"}}" alt="{{name}}">
<a href="{{url}}">{{name}}</a>
{{/primary_author}}
<time datetime="{{date format="YYYY-MM-DD"}}">
{{date format="DD MMM YYYY"}}
</time>
{{#if @custom.show_reading_time}}
<span>{{reading_time}}</span>
{{/if}}
</footer>
</div>
</article>
{{/foreach}}
{{pagination}}
Страница поста
{{! post.hbs}}
{{#post}}
<article class="{{post_class}}">
<header>
{{#unless primary_tag.name}}{{else}}
<a href="{{primary_tag.url}}" class="tag">{{primary_tag.name}}</a>
{{/unless}}
<h1>{{title}}</h1>
{{#if custom_excerpt}}
<p class="excerpt">{{custom_excerpt}}</p>
{{/if}}
<div class="meta">
{{#foreach authors}}
<a href="{{url}}">{{name}}</a>{{#unless @last}}, {{/unless}}
{{/foreach}}
·
<time>{{date published_at format="DD MMMM YYYY"}}</time>
· {{reading_time}}
</div>
</header>
{{#if feature_image}}
<figure class="hero-image">
{{!-- Ghost генерирует srcset автоматически --}}
<img src="{{img_url feature_image size="l"}}"
alt="{{#if feature_image_alt}}{{feature_image_alt}}{{else}}{{title}}{{/if}}">
{{#if feature_image_caption}}
<figcaption>{{feature_image_caption}}</figcaption>
{{/if}}
</figure>
{{/if}}
<div class="post-content gh-content">
{{content}}
</div>
{{! Блок для Members — только платный контент}}
{{#if access}}
<div class="premium-content">
{{content}}
</div>
{{else}}
<div class="paywall">
<h3>Эта статья для подписчиков</h3>
{{#unless @member}}
<a href="#/portal/signup" class="btn-subscribe">Подписаться</a>
{{/unless}}
</div>
{{/if}}
{{! Связанные посты}}
{{#get "posts" limit="3" filter="tag:[{{primary_tag.slug}}]+id:-{{id}}"}}
<section class="related-posts">
<h3>Похожие материалы</h3>
{{#foreach posts}}
{{> post-card}}
{{/foreach}}
</section>
{{/get}}
</article>
{{/post}}
Helper {{#get}} для динамических данных
{{! Последние посты по тегу на главной}}
{{#get "posts" limit="5" filter="tag:javascript" order="published_at desc"}}
{{#foreach posts}}
<a href="{{url}}">{{title}}</a>
{{/foreach}}
{{/get}}
{{! Количество подписчиков (публичная статистика)}}
{{#get "tiers" limit="all"}}
{{#foreach tiers}}
<div>{{name}}: доступно за {{monthly_price}}/мес</div>
{{/foreach}}
{{/get}}
Кастомные настройки темы
Ghost 5 позволяет редакторам менять параметры темы через Admin без кода:
{{! Использование custom setting}}
<header class="site-header style-{{@custom.header_style}}">
{{#if @custom.show_newsletter_signup}}
{{> newsletter-form}}
{{/if}}
</header>
Карточные блоки (Card CSS)
Ghost Koenig-редактор вставляет карточки (Gallery, Video, Bookmark, Product) с собственным HTML и CSS. Тема должна подключить эти стили:
// package.json
"config": {
"card_assets": true // Ghost автоматически добавит card CSS в <head>
}
Сборка и валидация
# Валидация темы (официальный инструмент Ghost)
npm install -g gscan
gscan /path/to/my-theme
# Или через API (CI/CD)
gscan /path/to/my-theme --json
# Загрузка через API
curl -X POST https://myblog.com/ghost/api/admin/themes/upload/ \
-H "Authorization: Ghost $ADMIN_API_KEY" \
-F "[email protected]"
Сроки разработки
| Тема | Время |
|---|---|
| Минимальная рабочая тема | 2–3 дня |
| Полноценная тема (index, post, tag, author) | 5–8 дней |
| Тема с Members/paywall и кастомными настройками | 8–12 дней |
| Тема с Newsletter-шаблонами | +1–2 дня |







