Menu
Sitemap

Sitemap

How to Generate Dynamic XML Sitemaps in Laravel Using Spatie

Learn how to create dynamic XML sitemaps in Laravel using the Spatie package. Step-by-step tutorial with routes, controllers, pagination, and SEO best practices.

Run the following command inside your Laravel project:
composer require spatie/laravel-sitemap

After installation, you can check your composer.json file. It should have:

"spatie/laravel-sitemap": "^6.0"

Ready to Use

add to routes/web.php
use App\Http\Controllers\SitemapController;

Route::get('/sitemap.xml', [SitemapController::class, 'index']);
Route::get('/product-sitemap-{page}.xml', [SitemapController::class, 'productSitemap'])->where('page', '[0-9]+');
Route::get('/pages-sitemap.xml', [SitemapController::class, 'pagesSitemap']);
Using -{page} style keeps URLs clean and prevents route collisions.

create app/Http/Controllers/SitemapController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\SitemapIndex;
use Spatie\Sitemap\Tags\Url;

class SitemapController extends Controller
{
/**
* Sitemap index: lists root sitemaps and paginated product sitemaps.
* URL: /sitemap.xml
*/
public function index()
{
$sitemapIndex = SitemapIndex::create();

// Add static/root sitemaps
$sitemapIndex->add(url('pages-sitemap.xml'));
$sitemapIndex->add(url('blogs-sitemap.xml')); // example static sitemap

// Add paginated sitemap files for products (dynamic)
$totalProducts = Cache::remember('product_sitemap_count', 3600, function () {
return DB::table('products')->where('is_active', 1)->count();
});

$perFile = 10000; // how many URLs per sitemap file
$totalPages = (int) ceil($totalProducts / $perFile);

for ($i = 1; $i <= $totalPages; $i++) {
$sitemapIndex->add(url("product-sitemap-{$i}.xml"));
}

// return XML response
return $sitemapIndex->render();
}

/**
* Paginated product sitemap.
* URL: /product-sitemap-1.xml, /product-sitemap-2.xml, ...
*/
public function productSitemap($page = 1)
{
$perFile = 10000; // same as used in index
$offset = ($page - 1) * $perFile;

$sitemap = Sitemap::create();

// NOTE: replace route('product.show', $row->slug) with your actual product URL generator
$rows = DB::table('products')
->where('is_active', 1)
->orderBy('id', 'asc')
->skip($offset)
->take($perFile)
->get();

foreach ($rows as $row) {
// Create a Url tag so we can set lastmod, changefreq, priority
$url = Url::create(url('product/' . $row->slug))
->setLastModificationDate($row->updated_at ?? now())
->setChangeFrequency(Url::CHANGE_FREQUENCY_DAILY)
->setPriority(0.8);

$sitemap->add($url);
}

return $sitemap->render();
}

/**
* Example: simple pages sitemap (non-paginated)
* URL: /pages-sitemap.xml
*/
public function pagesSitemap()
{
$sitemap = Sitemap::create();

$pages = DB::table('pages')->where('status', 1)->get();

foreach ($pages as $p) {
$sitemap->add(
url($p->slug),
$p->updated_at ?? now(),
'0.7',
'weekly'
);
}

return $sitemap->render();
}
}

Example output
/sitemap.xml (index)
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/pages-sitemap.xml</loc>
</sitemap>
<sitemap>
<loc>https://example.com/product-sitemap-1.xml</loc>
</sitemap>
<sitemap>
<loc>https://example.com/product-sitemap-2.xml</loc>
</sitemap>
...
</sitemapindex>

/product-sitemap-1.xml (paginated)
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/product/iphone-15</loc>
<lastmod>2025-09-10T12:00:00+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
...
</urlset>

Start Laravel dev server:
php artisan serve
# Visit:
# http://127.0.0.1:8000/sitemap.xml
# http://127.0.0.1:8000/product-sitemap-1.xml

Or test with curl:
curl -I http://127.0.0.1:8000/sitemap.xml
curl http://127.0.0.1:8000/product-sitemap-1.xml | xmllint --format -

Flow-chart laravel

Contact