Skip to main content

SEO Provider 개발 가이드

Seo plugin은 기본 site 설정만으로도 동작하지만, 콘텐츠 모델과 독립사이트 scope가 의미 있는 SEO를 제공하려면 provider를 등록해야 합니다.

Entity SEO Provider

Board Post, Commerce Product, Article 같은 콘텐츠 모델은 SeoMetaProviderInterface로 page/entity SEO를 제공합니다.

<?php

declare(strict_types=1);

namespace App\Sites\Newspaper\Seo;

use App\Plugins\Seo\Contracts\SeoMetaProviderInterface;
use App\Plugins\Seo\Data\SeoContext;
use App\Sites\Newspaper\Models\Article;

final class ArticleSeoProvider implements SeoMetaProviderInterface
{
public function supports(mixed $subject): bool
{
return $subject instanceof Article;
}

public function metaFor(mixed $subject, SeoContext $base): SeoContext
{
return $base->merge([
'title' => $subject->title,
'description' => $subject->excerpt,
'canonical_url' => route('articles.show', $subject, absolute: true),
'article' => [
'headline' => $subject->title,
'datePublished' => $subject->published_at?->toAtomString(),
'author' => ['type' => 'Person', 'name' => $subject->author_name],
],
]);
}
}

기존 모델이 seoMeta()를 제공하면 HasSeoMetaProvider bridge가 읽을 수 있습니다. 단, provider는 raw HTML이나 stringified JSON-LD를 반환하지 않습니다.

Scope Provider

동적 scope/page 설정은 SeoScopeProviderInterface로 제공합니다.

<?php

declare(strict_types=1);

namespace App\Sites\Scripture\Seo;

use App\Plugins\Seo\Contracts\SeoScopeProviderInterface;
use App\Plugins\Seo\Data\SeoScopeMatch;

final class ScriptureScopeProvider implements SeoScopeProviderInterface
{
public function supports(SeoScopeMatch $match): bool
{
return $match->siteSlug === 'scripture';
}

public function scopeSettings(SeoScopeMatch $match): ?array
{
return match ($match->scopeSlug) {
'dictionary' => [
'title' => '성경 사전',
'description' => '성경 용어와 원어 정보를 찾는 사전입니다.',
],
'typing' => [
'title' => '성경 타자연습',
'description' => '성경 구절로 타자 연습을 진행합니다.',
],
default => null,
};
}

public function pageSettings(SeoScopeMatch $match): ?array
{
return null;
}
}

등록:

app(\App\Plugins\Seo\Services\Scope\SeoScopeProviderRegistry::class)
->register(new \App\Sites\Scripture\Seo\ScriptureScopeProvider());

Sitemap Provider

콘텐츠 URL은 SeoSitemapProviderInterface로 sitemap에 공급합니다.

<?php

declare(strict_types=1);

namespace App\Sites\Newspaper\Seo;

use App\Plugins\Seo\Contracts\SeoSitemapProviderInterface;
use App\Plugins\Seo\Data\SeoContext;
use App\Plugins\Seo\Data\SeoSitemapUrl;
use App\Sites\Newspaper\Models\Article;
use Illuminate\Http\Request;

final class ArticleSitemapProvider implements SeoSitemapProviderInterface
{
public function urls(SeoContext $context, Request $request): iterable
{
foreach (Article::query()->published()->cursor() as $article) {
yield new SeoSitemapUrl(
loc: route('articles.show', $article, absolute: true),
lastmod: $article->updated_at?->toDateString(),
changefreq: 'daily',
priority: 0.8,
);
}
}
}

등록:

app(\App\Plugins\Seo\Services\Sitemap\SitemapRegistry::class)
->register(new \App\Sites\Newspaper\Seo\ArticleSitemapProvider());

Structured Data Provider

schema.org JSON-LD는 StructuredDataProviderInterface로 PHP array를 반환합니다.

<?php

declare(strict_types=1);

namespace App\Sites\Newspaper\Seo;

use App\Plugins\Seo\Contracts\StructuredDataProviderInterface;
use App\Plugins\Seo\Data\SeoContext;

final class NewspaperArticleStructuredDataProvider implements StructuredDataProviderInterface
{
public function graph(SeoContext $context): array
{
$data = $context->toArray();
$article = $data['article'] ?? null;

if (! is_array($article)) {
return [];
}

return [[
'@type' => 'NewsArticle',
'@id' => ($data['canonical_url'] ?? '').'#article',
'headline' => $article['headline'] ?? $data['title'] ?? null,
'datePublished' => $article['datePublished'] ?? null,
'author' => $article['author'] ?? null,
]];
}
}

등록:

app(\App\Plugins\Seo\Services\StructuredData\StructuredDataRegistry::class)
->register(new \App\Sites\Newspaper\Seo\NewspaperArticleStructuredDataProvider());

예시 파일

plugin 폴더의 docs/examples를 참고하세요.

  • BoardPostStructuredDataProviderExample.php
  • CommerceProductStructuredDataProviderExample.php
  • NewspaperArticleStructuredDataProviderExample.php
  • ScriptureScopeProviderExample.php
  • ScriptureStructuredDataProviderExample.php

테스트

SEO plugin 테스트는 _template 격리 DB를 사용하는 make test로 실행합니다.

make test NAME=_template ARGS="app/Plugins/Seo/tests/Unit"

provider를 SaaS에 붙이면 해당 SaaS의 route/controller feature test도 함께 추가하세요.