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
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.phpuse 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.xmlcurl http://127.0.0.1:8000/product-sitemap-1.xml | xmllint --format -
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 -