Bladeのガイド
通常のサーバーサイドレンダリングを行うアプリ向けに、このパッケージは7種類のBladeディレクティブを提供しています。多くの場合、そのうちの@seoだけで対応できます。
まとめて出力するディレクティブ
<!DOCTYPE html>
<html>
<head>
@seo($post)
</head>@seoは優先順位に従ってモデルの値を解決し、head内に必要な一式を出力します。対象は<title>、meta description、canonicalリンク、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()で絶対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複数のページ種別に対応するレイアウト
モデルのページ、ルートのページ、それ以外のページを1つのレイアウトで扱えます。
<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) | meta descriptionのみ |
@seoCanonical($post) | canonicalリンクのみ。未設定時は現在のURLを使用 |
@seoRobots($post) | robots metaのみ。明示的に出力を選ぶ呼び出しなので、常に出力されます。@seoとは異なり、デフォルト値との一致による出力抑制は適用されません |
@seoSchema($post) | JSON-LDの<script>のみ。head内でもbody内でも有効 |
いずれも@seoと同じ($model, $route, $locale)形式の引数を受け取ります。引数を省略すると、現在のページが対象になります。
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要素を途中で閉じることはできません。