Skip to content

Blueprint: Блог / новости (Blog)

Когда применять

«Новости», «статьи», «полезные материалы», «кейсы» — датируемый контент с рубриками и лентой. Один модуль; разные разделы («новости» и «статьи») — это PostCategory или поле type, не две модели.

Не применять для: страниц (см. pages), сущностей каталога с ценой.

Состав решения

  • Модели Post, PostCategory + миграции + фабрики
  • Контроллеры: PostController@index/show, категория — index с фильтром
  • Filament: PostResource, PostCategoryResource
  • Blade: лента (карточки + пагинация), статья, компонент «последние посты» для главной
  • Sitemap-интеграция, RSS (spatie/laravel-feed) — если по ТЗ
  • Тесты PostTest

Схема данных

posts

СтолбецТипИндексCast / примечание
ididPK
category_idFK nullable → post_categoriesconstrained + indexnullOnDelete
titlestring
slugstringunique
excerpttext nullableанонс для карточек; fallback — обрезка content
contentlongTextRichEditor HTML
statusenum(draft, published)indexPHP Enum; черновики обязательны
published_attimestamp nullableindex (+status composite)отложенная публикация
meta_title / meta_descriptionstring nullable
views_countunsignedInteger default 0если ТЗ хочет «популярное»
timestamps

Обложка — spatie/laravel-medialibrary (коллекция cover, конверсии card, hero), не столбец с путём.

post_categories: id, name, slug unique, sort_order, is_active, timestamps.

Правила реализации

  • Scope published: status = published AND published_at <= now() — единственный источник «что видно»; использовать везде включая sitemap/RSS/виджеты.
  • Роуты: /blog (лента), /blog/{category:slug} и /blog/{category:slug}/{post:slug} либо плоско /blog/{post:slug} — по ТЗ; выбранную схему зафиксировать 301-редиректами при изменении.
  • Лента: ->with('category') + medialibrary eager (->with('media')), пагинация из config, не хардкод.
  • published_at в вывод — translatedFormat('d F Y').
  • SEO: OG-теги с обложкой; Schema.org Article (headline, datePublished, image); посты и категории в sitemap.
  • views_count — инкремент без событий (increment), лучше через debounce-джоб или Redis-счётчик с периодическим сбросом в БД, чтобы не писать в таблицу на каждый просмотр под трафиком.
  • Кеш: тег posts, flush в afterSave обоих ресурсов.

Пакеты

spatie/laravel-medialibrary, spatie/laravel-sluggable (опц.), spatie/laravel-feed (RSS, если нужен).

Шаги

  1. Миграции + модели + Enum + scopes + фабрики.
  2. Filament-ресурсы (обложка через SpatieMediaLibraryFileUpload, отложенная публикация).
  3. Контроллеры + роуты с именами.
  4. Blade: лента @forelse, статья, виджет для главной.
  5. Sitemap + (опц.) RSS.
  6. Тесты: лента 200; черновик и будущая дата → 404 и нет в ленте; опубликованный → 200
    • h1/title; несуществующий slug → 404; sitemap содержит только published.

Чеклист приёмки

  • [ ] Черновики и отложенные посты не видны нигде (лента, прямая ссылка, sitemap, RSS, виджеты)
  • [ ] Лента без N+1 (категория и обложка загружены eager)
  • [ ] Обложки — конверсии medialibrary, не оригинал 4000px в карточке
  • [ ] Даты русские через translatedFormat
  • [ ] Article-разметка и OG с обложкой на странице поста

Внутренняя база знаний студии