Перейти к содержимому
ИИ

Мой первый блог

Ludo Fourrage Ludo Fourrage 8 апреля 2026 г. 8 мин чтения
Мой первый блог

Постановка задачи:

На платформе отсутствует канал контент‑маркетинга. Блог позволяет каждому тенанту публиковать SEO‑оптимизированные многоязычные статьи, создаваемые в Strapi CMS и отображаемые в приложении SvelteKit — это приводит органический трафик, укрепляет экспертный статус и поддерживает существующий i18n‑конвейер для 13 локалей.

Критерии успеха

  • Посты блога, созданные в Strapi, отображаются на фронтенде в брендинге арендатора
  • Посты автоматически переводятся на все 13 локалей через существующий webhook-конвейер (без изменений в инфраструктуре перевода)
  • Страницы блога ранжируются в поисковых системах (структурированные данные, sitemap, hreflang, og:article)
  • Арендаторы без фича-флага blog не видят маршруты блога (404)

User Stories

  • Как автор контента, я хочу писать в Strapi блог‑посты с расширенным markdown, изображениями и видео‑вставками, чтобы публиковать материалы без необходимости трогать код
  • Как посетитель сайта, я хочу просматривать блог‑посты с фильтром по категориям, чтобы находить релевантный контент
  • Как посетитель сайта, я хочу видеть оглавление в длинных постах, чтобы быстро по ним перемещаться
  • Как посетитель сайта, я хочу читать посты на своём языке, с автоматическим переводом с английского
  • Как посетитель сайта, я хочу делиться постами в социальных сетях
  • Как посетитель сайта, я хочу находить связанные посты и узнавать больше об авторе
  • Как SEO‑менеджер, я хочу, чтобы у каждого поста были корректные метатеги, структурированные данные и записи в sitemap

Функциональные требования

Обязательные (MVP)

  1. Типы контента Strapi: blog-posts, blog-authors, blog-categories — все с полями tenant, locale, inherit
  2. Страница списка блога (/blog) — постраничная сетка постов, фильтр по категории через query‑параметр
  3. Страница поста (/blog/[slug]) — полный рендеринг markdown с оглавлением (ToC), карточкой автора, связанными постами, кнопками шаринга в соцсетях
  4. Страница категории (/blog/category/[slug]) — отфильтрованный список с переиспользованием тех же компонентов списка
  5. Страница автора (/blog/author/[slug]) — био автора + его/её посты
  6. Оглавление (Table of Contents) — авто‑генерация из заголовков h2/h3, «липкий» сайдбар на десктопе, сворачиваемый блок на мобильных, подсветка активного заголовка через Intersection Observer
  7. Встраивание YouTube/Vimeo — «голые» URL в markdown автоматически конвертируются в адаптивные iframes (youtube-nocookie.com для приватности)
  8. Время чтения — рассчитывается на сервере по количеству слов (Math.ceil(words / 200))
  9. Кнопки шаринга — X, Facebook, LinkedIn, email, копирование ссылки (Web Share API, где доступно)
  10. Похожие посты — та же категория, исключая текущий, максимум 3, сортировка по publishedAt по убыванию
  11. RSS‑лента (/blog/rss.xml) — RSS 2.0, локаль через query‑параметр
  12. SEO — SeoComponent для каждого поста, теги og:article, схема BlogPosting JSON‑LD, канонические URL
  13. Карта сайта — динамическое добавление URL постов/категорий/авторов блога в существующую sitemap с hreflang
  14. Фича‑флагblog: boolean в TenantConfig.features, guard в layout возвращает 404 при выключенном флаге
  15. i18n — ключи сообщений Paraglide для всех UI‑строк (навигация, подписи, пагинация), контент Strapi авто‑переводится через существующий webhook
  16. Пагинация — постраничная, с использованием нативной пагинации Strapi (9 постов на страницу)
  17. Избранный пост — булевый флаг featured на blog-posts; избранный пост отображается как hero‑карточка вверху страницы списка /blog (увеличенная карточка, на всю ширину, над основной сеткой)

Желательно (после MVP)

  1. Полнотекстовый поиск по постам блога
  2. Серии постов / многочастные статьи
  3. Индикатор прогресса чтения
  4. Комментарии (скорее всего через сторонний виджет)
  5. CTA подписки на рассылку внутри постов
  6. Счётчик просмотров / интеграция с аналитикой

Технические требования

Модель данных (типы контента Strapi)

blog-posts (api::blog-post.blog-post):

Поле Тип Локализуется Примечания
title Text (required) Yes Соответствует конфигу переводов
slug UID (from title) No Исключено из перевода
excerpt Text Yes Для карточек + мета-описания
body Rich Text (Markdown) Yes Рендерится через marked
featuredImage Media (single) No URL + alternativeText
category Relation (manyToOne -> blog-category) No
author Relation (manyToOne -> blog-author) No
featured Boolean (default false) No Избранный пост, показан как hero-карта в списке
seo Component (shared.seo) Yes Повторное использование существующего SeoComponent
tenant Text No Стандартный фильтр tenant
inherit Boolean (default true) No Флаг авто-перевода

blog-authors (api::blog-author.blog-author):

Поле Тип Локализуется Примечания
name Text (required) Yes
slug UID (from name) No
bio Rich Text (Markdown) Yes
avatar Media (single) No
socialLinks JSON No { twitter?, linkedin?, website? }
tenant Text No
inherit Boolean (default true) No

blog-categories (api::blog-category.blog-category):

Поле Тип Локализуется Примечания
name Text (required) Yes
slug UID (from name) No
description Text Yes
color Text No Hex-цвет для UI-бейджей
tenant Text No
inherit Boolean (default true) No

Интерфейсы TypeScript

См. src/lib/services/strapi-types.ts для BlogPost, BlogAuthor, BlogCategory.

Маршруты

Все под src/routes/(marketing)/blog/:

Маршрут Server Load Назначение
blog/+layout.server.ts Feature guard + загрузка категорий Гейт + навигационные данные
blog/+page.server.ts Постраничные записи, необязательный фильтр ?category= Список
blog/[slug]/+page.server.ts Пост по slug + связанные посты + расчёт времени чтения Детальная страница
blog/category/[slug]/+page.server.ts Посты, отфильтрованные по категории Список по категории
blog/author/[slug]/+page.server.ts Автор + его/её посты Страница автора
blog/rss.xml/+server.ts RSS 2.0 XML, параметр ?locale= Лента

UI‑компоненты

Все в src/lib/components/blog/:

Компонент Props Примечания
BlogPostCard.svelte post: BlogPost Карточка с изображением, бейджем категории, заголовком, описанием, автором, датой, временем чтения
BlogPostContent.svelte body: string Markdown в HTML через marked с расширением для встраивания видео + ID для заголовков
TableOfContents.svelte entries: TocEntry[] Прикреплённый сайдбар, Intersection Observer для активного состояния, сворачиваемый на мобильных
AuthorCard.svelte author: BlogAuthor Аватар, имя, био, ссылки на соцсети
SocialShare.svelte url: string, title: string Кнопки шаринга с запасным вариантом на Web Share API
RelatedPosts.svelte posts: BlogPost[] Сетка из BlogPostCard
BlogPagination.svelte pagination: StrapiPagination, basePath: string Предыдущая/следующая + номера страниц

Безопасность и аутентификация

  • Блог публичный (аутентификация не требуется) — маркетинговый контент
  • Доступ на чтение к Strapi API через существующий токен STRAPI_JWT
  • Видеовстраивания используют youtube-nocookie.com для соблюдения требований GDPR

Частные случаи и обработка ошибок

  • Пост не найден: 404 через SvelteKit error()
  • Нет постов в категории: пустое состояние с сообщением blog_no_posts
  • Отсутствует избранное изображение: карточка отображается без изображения
  • Перевод недоступен: loadContentWithFallback() откатывается на английский
  • RTL‑языки: макет учитывает dir="rtl" (ar, he, ur)

План E2E‑тестирования

Тестовые файлы находятся в tests/blog/. Следуйте существующим паттернам: параметризация по вьюпортам, Axe для a11y, networkidle ожидает контент из Strapi.

Тесты критического пути

  1. Загрузка списка блога/blog рендерит сетку карточек постов с заголовком, выдержкой, бейджем категории, автором, датой и временем чтения
  2. Работа пагинации/blog?page=2 показывает вторую страницу результатов; кнопки «предыдущая/следующая» корректно переходят между страницами; на странице 1 нет кнопки «Previous»
  3. Фильтр по категории — клик по чипу категории фильтрует посты; URL обновляется до ?category=<slug>; чип «All» сбрасывает фильтр
  4. Отрисовка страницы поста/blog/[slug] показывает заголовок, обложку (featured image), форматированное тело, карточку автора, время чтения и дату публикации
  5. Оглавление (ToC) — на странице поста генерируется ToC из заголовков h2/h3; клик по пункту ToC скроллит к соответствующему заголовку
  6. Похожие посты — на странице поста отображается до 3 похожих постов из той же категории (без текущего поста)
  7. Страница категории/blog/category/[slug] показывает название категории, описание и только посты из этой категории
  8. Страница автора/blog/author/[slug] показывает био автора, аватар, ссылки на соцсети и его/её посты
  9. Кнопки шаринга в соцсетях — на странице поста видны кнопки шаринга; «Copy link» копирует URL и показывает подтверждающее сообщение
  10. RSS‑лентаGET /blog/rss.xml возвращает валидный XML с Content-Type: application/rss+xml и содержит записи постов
  11. Навигация — ссылка на блог в меню сайта ведёт на /blog; хлебные крошки отображают корректную иерархию
  12. Избранный пост/blog отображает избранный пост как крупную hero‑карточку над основной сеткой

Тесты краевых случаев и ошибок

  1. Пост не найден/blog/nonexistent-slug возвращает страницу 404
  2. Пустая категория — категория без постов показывает сообщение пустого состояния blog_no_posts
  3. Отсутствует обложка — карточка поста корректно рендерится без изображения (нет битого <img>)
  4. Отсутствует автор/категория — пост без автора или категории рендерится без этих элементов (без падения)
  5. Фича‑флаг выключен — когда blog: false, /blog возвращает 404

Сквозные тесты

  1. i18n: переключение локали — перейти на /fr/blog, проверить, что UI‑строки (заголовок страницы, «Read more», подписи пагинации) на французском; контент поста по умолчанию на английском, если нет перевода
  2. i18n: RTL‑верстка — перейти на /ar/blog и /ar/blog/[slug], проверить, что установлен dir="rtl" и верстка отображается корректно (ToC на правильной стороне)
  3. SEO: мета‑теги — страница поста содержит og:type=article, og:title, og:description, article:published_time и canonical‑ссылку
  4. SEO: JSON‑LD — страница поста содержит <script type="application/ld+json"> с @type: BlogPosting, headline, author, datePublished
  5. SEO: sitemap/sitemap.xml включает URL‑ы постов блога с hreflang‑альтернативами
  6. Доступность — запустить Axe‑аудит на /blog, /blog/[slug], /blog/author/[slug] для мобильного и десктопного вьюпорта (следует существующему паттерну a11y‑тестов)
  7. Клавиатурная навигация — пункты ToC и кнопки пагинации доступны в фокусе и активируются с клавиатуры; индикаторы фокуса видимы

Вне области проекта

  • Комментарии к записям блога
  • Полнотекстовый поиск
  • Интеграция рассылки внутри записей
  • Аналитика / счётчики просмотров
  • Индивидуальные шаблоны записей для каждого арендатора
Ludo Fourrage
Ludo Fourrage

Ludo — преподаватель и предприниматель в сфере технологий, которым движет простая идея: качественное образование должно быть доступно каждому.