Laravel サイトで sitemap.xml を手動更新していると、新しいページを追加したのに sitemap に反映し忘れてしまうことがあります。
本記事では、Spatie の Laravel Sitemap パッケージを利用して、Laravel のルート情報から自動的に sitemap.xml を生成する方法を解説します。
「sitemap の SEO 上の役割」から、「パッケージ導入」「ルートスキャンを使ったコマンド実装」「スケジューラでの自動実行」「Search Console への登録」まで、ひと通りの流れをコード付きで確認できる構成にしました。
なぜsitemap.xmlの自動生成が必要なのか
sitemapの役割とSEOにおける位置づけ
sitemap.xml は、サイト内の URL と更新情報を一覧で記述した XML ファイルです。
Google などの検索エンジンはこのファイルを読み取り、サイト全体の構造や重要なページを効率よく把握します。
特に以下のようなサイトでは、sitemap を用意しておくことでインデックスの精度向上が期待できます。
- ページ数が多い、または階層が深いサイト
- 内部リンクだけではクローラーが到達しづらいページがあるサイト
- 頻繁に記事や機能ページを追加・更新するサイト
手動更新の課題
sitemap を手動で管理していると、次のような問題が起きがちです。
- 新規ページの追加時に sitemap への登録を忘れる
- 不要になったページの URL を削除し忘れる
- 更新日時(lastmod)を適切にメンテナンスできない
これらはクローラーの巡回効率を落とし、重要ページが十分に評価されない要因になり得ます。
そこで、アプリケーション側で sitemap を自動生成する仕組みを用意しておくことが、SEO のベースラインとして有効です。
Spatie Sitemapとは何か(Laravel向けSitemap自動生成パッケージ)
Spatie Sitemapは、Laravel 向けに提供されている sitemap.xml 自動生成パッケージです。
主な特徴は以下の通りです。
- サイトをクロールして自動的に URL を収集できる
- コードから任意の URL を追加・除外できる
- lastmod・priority・changefreq など sitemap 固有の情報を柔軟に設定できる
- コマンドやスケジューラと組み合わせて、定期的に sitemap.xml を再生成できる
公式の README ではクロールベースの生成方法が紹介されていますが、本記事では 「Laravel のルート定義から sitemap.xml を生成する」 実装方法にフォーカスします。
Spatie(スパティ)は、ベルギーのLaravel特化開発会社で、Laravelエコシステムで最も信頼性の高いOSSパッケージを多数提供しています。
導入〜コマンド実装
Spatie Sitemapの導入手順(インストール〜設定)
まずは Composer でパッケージをインストールします。
composer require spatie/laravel-sitemap
Laravel 10 以降であれば、サービスプロバイダの登録は自動で行われます。
ルートスキャンでsitemap.xmlを自動生成するコマンド実装
ここからは、Laravel の Route::getRoutes() を利用し
- GET ルートのみを対象にする
- 動的パラメータ付きのルート(
{id}など)は除外する apiやadminなど管理系・API ルートを除外する
といった条件で URL を抽出し、Spatie Sitemap に渡していくコマンドを実装します。
実装するコマンドのシグネチャは次の通りです。
php artisan sitemap:generatephp artisan sitemap:generate --path=public/sitemap.xmlphp artisan sitemap:generate --base-url=https://example.com
これにより、環境や出力先に応じて柔軟に sitemap を生成できるようになります。
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Throwable;
class GenerateSitemap extends Command
{
/**
* php artisan sitemap:generate
* php artisan sitemap:generate --path=public/sitemap.xml
* php artisan sitemap:generate --base-url=https://example.com
*/
protected $signature = 'sitemap:generate
{--path= : Output path (default: public_path("sitemap.xml"))}
{--base-url= : Base URL (default: config("app.url"))}';
protected $description = 'Generate sitemap.xml from Laravel routes (no HTTP crawling).';
public function handle(): int
{
$outputPath = (string) ($this->option('path') ?: public_path('sitemap.xml'));
// ▼ ベースURLの決定(オプション優先 → config("app.url") フォールバック)
$baseUrl = (string) ($this->option('base-url') ?: config('app.url'));
if (empty($baseUrl)) {
$this->error('Base URL not found. Please specify --base-url option or set config("app.url").');
return SymfonyCommand::FAILURE;
}
// 末尾スラッシュを除去しておく(https://s-okada.org/ → https://s-okada.org)
$baseUrl = rtrim($baseUrl, '/');
$this->info("Generating sitemap from routes...");
$this->info("Output: {$outputPath}");
$this->info("Base URL: {$baseUrl}");
try {
$sitemap = Sitemap::create();
foreach (Route::getRoutes() as $route) {
// 1. GET 以外のメソッドは除外
if (! in_array('GET', $route->methods(), true)) {
continue;
}
$uri = $route->uri(); // 例: "/", "about", "articles/{id}"
// 2. 動的パラメータ付きルートは除外({id} など)
if (Str::contains($uri, ['{', '}'])) {
continue;
}
// 3. API / 管理画面 / デバッグ用ルートなどを除外
if (Str::startsWith($uri, ['api', 'admin', '_debugbar', 'up'])) {
continue;
}
// URL → Blade推定パスへ変換
$bladePath = str_replace('/', '.', $uri); // 'pages.about'
$bladePath = $bladePath === '.' ? 'welcome' : $bladePath;
// 実ファイルパス
$filePath = match ($bladePath) {
// 見つけられないものをマッピングする。
default => resource_path("views/{$bladePath}.blade.php"),
};
if (file_exists($filePath)) {
$lastmod = \Carbon\Carbon::createFromTimestamp(filemtime($filePath));
} else {
// ファイルが見つからない場合は現在時刻
$lastmod = now();
$this->info(
'file not fund:' . $filePath
);
}
// 4. 実際のURLに変換
// "/" はそのまま、他は "/xxx" に整形
$path = $uri === '/' ? '/' : '/' . ltrim($uri, '/');
$url = $baseUrl . $path;
$priority = match ($path) {
'/' => 1.0,
default => 0.8,
};
$changeFrequency = match ($path) {
'/' => 'daily',
default => 'weekly',
};
$sitemap->add(
Url::create($url)
->setLastModificationDate($lastmod)
->setPriority($priority)
->setChangeFrequency($changeFrequency)
);
}
$sitemap->writeToFile($outputPath);
$this->info('Sitemap generation completed successfully (from routes).');
return SymfonyCommand::SUCCESS;
} catch (Throwable $e) {
$this->error('An error occurred while generating sitemap.');
$this->error($e->getMessage());
report($e);
return SymfonyCommand::FAILURE;
}
}
}
スケジューラで定期的にsitemap.xmlを更新する
Laravel のタスクスケジューラを使うことで、本番環境で定期的に sitemap.xml を再生成できます。
<?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
// sitemap:generate
Schedule::command('sitemap:generate --base-url=https://example.com')
->cron('40 2 * * *')
->timezone('Asia/Tokyo');
サイトの更新頻度に応じて、daily や weekly など適切な頻度を設定してください。
Google Search Console の「サイトマップ」メニューから登録しておくと、クロール状況やエラーを確認できます。