spatie/laravel-sitemap
Generate XML sitemaps for Laravel automatically by crawling your site or building them manually. Add URLs, models, lastmod/changefreq/priority, images and alternates, then write to file or disk. Supports sitemap index and large sites.
Installation:
composer require spatie/laravel-sitemap
The package auto-registers.
Basic Crawl & Generate:
use Spatie\Sitemap\SitemapGenerator;
SitemapGenerator::create('https://example.com')
->writeToFile(public_path('sitemap.xml'));
First Use Case:
public/sitemap.xml and validate it using Google's Sitemap Tester.Crawl-Based Generation:
SitemapGenerator::create('https://example.com')
->setMaximumCrawlCount(1000) // Limit depth
->setConcurrency(5) // Parallel requests
->writeToFile(public_path('sitemap.xml'));
Hybrid Approach (Crawl + Manual URLs):
$generator = SitemapGenerator::create('https://example.com');
$sitemap = $generator->getSitemap();
// Add manual URLs (e.g., admin pages)
$sitemap->add(Url::create('/admin')->setChangeFrequency(Url::CHANGE_FREQUENCY_NEVER));
$sitemap->writeToFile(public_path('sitemap.xml'));
Model-Based Sitemaps (via Sitemapable):
// In Post.php
class Post implements Sitemapable {
public function toSitemapTag(): Url {
return Url::create(route('posts.show', $this))
->setLastModificationDate($this->updated_at)
->setChangeFrequency(Url::CHANGE_FREQUENCY_WEEKLY);
}
}
// Generate via:
SitemapGenerator::create('https://example.com')
->getSitemap()
->add(Post::all()->map(fn($post) => $post->toSitemapTag()))
->writeToFile(public_path('posts_sitemap.xml'));
Scheduled Generation (via Artisan Command):
// app/Console/Commands/GenerateSitemap.php
class GenerateSitemap extends Command {
public function handle() {
SitemapGenerator::create(config('app.url'))
->writeToFile(public_path('sitemap.xml'));
}
}
// Schedule in routes/console.php:
Schedule::command('sitemap:generate')->dailyAt('2:00');
Dynamic URL Filtering:
SitemapGenerator::create('https://example.com')
->hasCrawled(fn(Url $url) => !Str::contains($url->getAbsoluteUrl(), ['/login', '/admin']))
->writeToFile(public_path('sitemap.xml'));
Multilingual Sitemaps:
$sitemap = Sitemap::create();
$sitemap->add(
Url::create('/post')
->addAlternate('/post-fr', 'fr')
->addAlternate('/post-es', 'es')
);
News Sitemaps:
$sitemap->add(
Url::create('/news/breaking')
->addNews('My News Site', 'en', 'Breaking News', now())
);
Sitemap Indexing (for large sites):
SitemapIndex::create()
->add('/posts_sitemap.xml')
->add('/pages_sitemap.xml')
->writeToDisk('public', 'sitemap-index.xml');
Headless Chrome for JS-Rendered Pages:
// config/sitemap.php
'execute_javascript' => true,
'chrome_binary_path' => '/path/to/chrome',
// Requires:
composer require spatie/browsershot
Crawl Depth Limits:
->depth(3) or ->setMaximumCrawlCount(500) to constrain crawling.Robots.txt Overrides:
robots.txt by default.->ignoreRobots() if needed (e.g., for internal pages).Concurrency Issues:
->setConcurrency(2) for sensitive APIs.Dynamic Content:
execute_javascript (requires spatie/browsershot).Duplicate URLs:
->hasCrawled() to deduplicate:
->hasCrawled(fn(Url $url) => !collect($crawledUrls)->contains($url->getAbsoluteUrl()))
File Permissions:
public/ may fail if permissions are restrictive.storage:link is run and public/ is writable.Log Crawled URLs:
SitemapGenerator::create('https://example.com')
->hasCrawled(fn(Url $url) => {
Log::debug('Crawled:', [$url->getAbsoluteUrl()]);
return $url;
})
->writeToFile(...);
Validate Sitemap:
lastmod dates.Profile Crawl Performance:
$start = microtime(true);
SitemapGenerator::create('https://example.com')->writeToFile(...);
Log::info('Sitemap generated in ' . (microtime(true) - $start) . 's');
Custom Crawl Profiles:
Extend Spatie\Sitemap\Crawler\Profile to modify crawl behavior:
class CustomProfile extends Profile {
public function shouldCrawl(string $url): bool {
return !Str::contains($url, ['/private']);
}
}
// Usage:
SitemapGenerator::create('https://example.com')
->setCrawlProfile(new CustomProfile());
Custom URL Tags:
Extend Spatie\Sitemap\Tags\Url for custom XML attributes:
class CustomUrl extends Url {
public function addCustomAttribute(string $name, string $value): self {
$this->customAttributes[$name] = $value;
return $this;
}
}
Pre/Post-Crawl Hooks: Use Laravel events to hook into the process:
// In EventServiceProvider:
protected $listen = [
'sitemap.generating' => [SitemapHook::class, 'onGenerating'],
'sitemap.generated' => [SitemapHook::class, 'onGenerated'],
];
Cache Warmup: Pre-generate sitemaps during deployments:
// In Deployer task:
task('deploy:sitemap', function() {
run('php artisan sitemap:generate');
});
guzzle_options:
'guzzle_options' => [
'timeout' => 30,
'headers' => ['User-Agent' => 'MySitemapBot/1.0'],
],
Chrome Binary Path:
execute_javascript is true, ensure chrome_binary_path is set correctly.which chrome (Linux/macOS) or where chrome (Windows) to locate the binary.Disk Writing:
writeToDisk() uses Laravel’s filesystem. Ensure the disk is configured in config/filesystems.php.->writeToDisk('s3', 'sitemap.xml', true) // Public visibility
How can I help you explore Laravel packages today?