Руководство по Blade
Для классических приложений с серверным рендерингом пакет предоставляет семь Blade-директив. Обычно достаточно одной из них — @seo.
Универсальная директива
<!DOCTYPE html>
<html>
<head>
@seo($post)
</head>@seo получает значения модели через цепочку приоритетов и выводит полный блок head: <title>, метаописание, каноническую ссылку, robots, теги Open Graph и Twitter Card, а также прикреплённый JSON-LD. Тег robots выводится только при отличии от значения сайта по умолчанию: избыточный index,follow пропускается (его отсутствие уже означает index,follow). Задайте seo.robots.emit_default, чтобы выводить его всегда. См. полный контракт рендеринга.
Сигнатуры:
@seo($post) {{-- model page --}}
@seo($seoData) {{-- a hand-built SEOData (model-less page) --}}
@seo($post, 'blog.show') {{-- model + route defaults --}}
@seo($post, null, 'fr') {{-- model + locale --}}
@seo(null) {{-- current page, no model --}}@seo принимает Model, созданный вручную SEOData или null. Аргументы маршрута и локали применяются только при передаче Model/null: созданный вручную SEOData содержит собственные значения.
Страницы маршрутов (без модели)
Для статических страниц, архивов и других страниц, определяемых маршрутом:
@seoForRoute('pages.about')
@seoForRoute('contact', 'de') {{-- with locale --}}Значения маршрута берутся из строк seo_defaults, привязанных к имени маршрута.
Страницы без модели: созданный вручную SEOData
Списки, результаты поиска и страницы, собранные в контроллере, часто не связаны с одной моделью. Создайте SEOData и передайте его прямо в @seo (или фасад SEO) — обращаться к app(TagRenderer::class)->render(...) не требуется:
use Rankbeam\Seo\Data\SEOData;
return view('search.results', [
'seo' => new SEOData(
title: "Results for \"{$query}\"",
description: "Browse {$count} matches for {$query}.",
ogImage: '/images/search-share.jpg', // relative is fine — see below
),
]);<head>
@seo($seo)
</head>Созданный вручную SEOData трактуется как явно заданное намерение. Все заданные значения сохраняются; автоматически заполняются только пробелы, возникающие при выводе:
canonical/og:urlпри отсутствии вычисляются из текущего URL (явныйcanonicalсохраняется без изменений, включая строку запроса);title_suffixдобавляется, только если его нет в заголовке (и полностью пропускается, если заголовок уже содержит маркер бренда — см.title_suffix_skip_when_contains);- относительные пути
og:image/twitter:imageпреобразуются в абсолютные черезurl()(с сохранением текущей схемы — HTTPS не навязывается); og:site_nameиlocaleзаполняются из конфигурации / локали приложения.
Цепочка приоритетов базы данных (глобальные значения / значения для типа модели / маршрута / seo_meta) не объединяется с созданным вручную SEOData: выводится то, что вы передали, с заполнением только перечисленных пробелов.
Тот же объект работает через фасад:
SEO::render($seoData); // HTML string
SEO::toArray($seoData); // Vue/React structure
SEO::forInertia($seoData); // Inertia Head structureШаблон, который подходит для разных страниц
Один шаблон для страниц моделей, страниц маршрутов и всех остальных:
<head>
@if(isset($seoModel))
@seo($seoModel)
@elseif(isset($seoRoute))
@seoForRoute($seoRoute)
@else
@seo(null)
@endif
</head>После этого контроллеры передают 'seoModel' => $post или 'seoRoute' => 'blog.index' и не работают с разметкой.
Отдельные директивы
Если требуется управлять отдельными тегами (например, при сочетании с выводом другого пакета):
| Директива | Что выводит |
|---|---|
@seoTitle($post) | Только <title> |
@seoMeta($post) | Только метаописание |
@seoCanonical($post) | Только каноническую ссылку (при отсутствии значения используется текущий URL) |
@seoRobots($post) | Только метатег robots — выводится всегда (это явное включение, поэтому подавление при совпадении со значением по умолчанию, как у @seo, не применяется) |
@seoSchema($post) | Только JSON-LD <script> — допустим в head и body |
Все они принимают то же выражение ($model, $route, $locale), что и @seo, либо вызываются без аргумента для текущей страницы.
Альтернативные языковые версии hreflang
Модели с HasSEO могут передавать ссылки hreflang напрямую через резолвер:
public function getSEOAlternates(): ?array
{
return [
['hreflang' => 'en', 'href' => route('posts.show', ['locale' => 'en', 'post' => $this])],
['hreflang' => 'it', 'href' => route('posts.show', ['locale' => 'it', 'post' => $this])],
];
}Используйте абсолютные URL. @seo($post) обрабатывает эти записи и выводит каждую как <link rel="alternate" hreflang="..." href="...">. Сначала коды приводятся к форме BCP 47 (it_IT → it-IT), а политики seo.hreflang могут добавить ссылку страницы на саму себя и x-default; бесплатный аудит отмечает неверные и повторяющиеся записи, а также отсутствие ссылки на саму страницу. См. многоязычный контент.
Экранирование и безопасность
Текстовые значения экранируются через e(). JSON-LD кодируется с JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP, поэтому </script> внутри пользовательского контента не может выйти за пределы элемента script.