Laravelでsitemap.xmlを自動生成する方法|Spatie Sitemap+ルートスキャン実装例

/ 更新:

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} など)は除外する
  • apiadmin など管理系・API ルートを除外する

といった条件で URL を抽出し、Spatie Sitemap に渡していくコマンドを実装します。

実装するコマンドのシグネチャは次の通りです。

  • php artisan sitemap:generate
  • php artisan sitemap:generate --path=public/sitemap.xml
  • php 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');

サイトの更新頻度に応じて、dailyweekly など適切な頻度を設定してください。

Google Search Console の「サイトマップ」メニューから登録しておくと、クロール状況やエラーを確認できます。

About Shinya Okada

1989年生まれ。既婚。東京高専・茨城大。
グループ会社SE→社内SEへ転職。
趣味:バレーボール、投資、プログラミング