Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sitemap Laravel Package

samdark/sitemap

A lightweight sitemap generator for Yii2 applications. Build XML sitemaps and sitemap indexes, add URLs with last-modified, change-frequency, and priority metadata, and write or cache the output. Supports gzip compression and straightforward integration with Yii2 projects.

Deep Wiki
Context7

Getting Started

samdark/sitemap is a dependency-free sitemap writer. It does not crawl, query your database, or know anything about Laravel — you hand it URLs and it writes spec-compliant XML. That makes it a small, predictable piece of infrastructure rather than a framework to adopt.

composer require samdark/sitemap

The smallest useful integration is an Artisan command that writes a static file:

use samdark\sitemap\Sitemap;

$sitemap = new Sitemap(public_path('sitemap.xml'));
$sitemap->addItem(url('/'), time(), Sitemap::DAILY, 1.0);
$sitemap->addItem(route('posts.index'), time(), Sitemap::WEEKLY, 0.8);
$sitemap->write();

Look first at:

  • src/Sitemap.php — addItem() signature and the ALWAYS/HOURLY/DAILY/WEEKLY/MONTHLY/YEARLY/NEVER constants.
  • src/SitemapIndex.php — for splitting large sites across multiple files.
  • src/extensions/ — image, video, news, mobile, and alternates support if your version ships them.
  • The README examples — the API is intentionally small, so the README is usually enough.

Your first real use case should be a scheduled command, not a controller. Generate public/sitemap.xml (or public/sitemaps/sitemap.xml) at deploy or nightly, reference it from robots.txt, and submit it once to Search Console. From there, the main design question is whether you need one file or an index.

Implementation Patterns

Command-driven generation. Keep generation out of the request cycle. A command is testable, schedulable, and safe to run during deploys.

class GenerateSitemap extends Command
{
    protected $signature = 'sitemap:generate';

    public function handle(): int
    {
        $dir = public_path('sitemaps');
        File::ensureDirectoryExists($dir);

        $sitemap = new Sitemap($dir . '/pages.xml');
        $sitemap->addItem(url('/'), now()->timestamp, Sitemap::DAILY, 1.0);
        $sitemap->addItem(route('about'), now()->timestamp, Sitemap::MONTHLY, 0.5);
        $sitemap->write();

        return self::SUCCESS;
    }
}
$schedule->command('sitemap:generate')->dailyAt('03:15')->withoutOverlapping();

Sitemap index for anything large. Hard limits are 50,000 URLs and 50 MB uncompressed per sitemap, so chunk at ~45,000 to leave headroom. Use cursor() or chunkById() to avoid loading models into memory.

$index = new SitemapIndex($dir . '/sitemap.xml');
$chunk = 1;
$count = 0;
$sitemap = new Sitemap("$dir/posts-$chunk.xml");

Post::where('published', true)->orderBy('id')->cursor()->each(
    function (Post $post) use (&$sitemap, &$chunk, &$count, $index, $dir) {
        $sitemap->addItem(
            route('posts.show', $post),
            $post->updated_at->timestamp,
            Sitemap::WEEKLY,
            0.7
        );

        if (++$count % 45000 === 0) {
            $sitemap->write();
            $index->addSitemap(url("sitemaps/posts-$chunk.xml"));
            $chunk++;
            $sitemap = new Sitemap("$dir/posts-$chunk.xml");
        }
    }
);

if ($count % 45000 !== 0) {
    $sitemap->write();
    $index->addSitemap(url("sitemaps/posts-$chunk.xml"));
}

$index->write();

Gzip when bandwidth or file size matters. Pass true as the second constructor argument and use a .xml.gz filename. The index must reference the same .gz path.

$sitemap = new Sitemap($dir . '/products.xml.gz', true);

Serve static files from the web server. public/sitemaps/*.xml served by nginx/Apache is cheaper and more cacheable than a Laravel route. Use a route only when URLs must be generated per request:

Route::get('/sitemap.xml', fn () => response()->file(
    public_path('sitemaps/sitemap.xml'),
    ['Content-Type' => 'application/xml']
));

Regenerate on content changes, debounced. Model events that synchronously regenerate the sitemap will punish bulk imports. Queue a unique, debounced job instead:

Post::saved(fn () => GenerateSitemap::dispatch()->delay(now()->addMinutes(5)));

Use ShouldBeUnique and WithoutOverlapping so a 10,000-row import produces one regeneration, not 10,000.

Use extensions for rich result schemas. Image, video, and news sitemaps require additional XML namespaces and per-URL metadata. The package extensions handle the namespace wiring; the exact addExtension()/payload signatures vary by version, so read src/extensions/ for the release you install rather than copying old blog posts.

Test the output, not the internals.

$this->artisan('sitemap:generate')->assertSuccessful();
$this->assertFileExists(public_path('sitemaps/sitemap.xml'));
$this->assertStringContainsString('<loc>', file_get_contents(public_path('sitemaps/sitemap.xml')));

Deployment tip. Run sitemap:generate in your deploy script after migrations and cache warmup. If generation fails, the old file remains and search engines keep working — which is usually preferable to serving an empty sitemap.

Gotchas and Tips

  • The 50,000 URL / 50 MB limits are per file, not per site. Exceeding them silently produces an invalid sitemap. Use an index once you pass a few thousand dynamic URLs.
  • Sitemap accumulates items in memory. It is a builder, not a streaming writer. Chunking with cursor() reduces model memory, but the sitemap object itself still holds every entry until write(). Write every 45k rows and start a new instance.
  • All URLs must be absolute, same-host, and canonical. route() depends on APP_URL; behind a proxy or load balancer, add URL::forceScheme('https') in a service provider or your sitemap will contain http:// URLs from the wrong host.
  • Pass a Unix timestamp for lastModified. Newer versions may accept DateTimeInterface — check the installed signature. Passing a formatted date string where an integer is expected produces a 1970 timestamp or invalid XML.
  • Timezone affects output. W3C datetimes include an offset. If your app timezone is not UTC, either set APP_TIMEZONE=UTC or accept that the offset is emitted; do not mix server-local timestamps with UTC expectations.
  • No deduplication. If a query can return the same URL twice (joins, polymorphic relations, multiple categories), you will emit duplicate <url> entries. Deduplicate with a seen set or unique('url') before adding.
  • The package escapes XML entities, so don't pre-escape. URLs with & are handled. Invalid UTF-8 in slugs will still break the document — sanitize or skip those rows.
  • Check writability before blaming the package. write() needs the directory to exist and be writable by the PHP/deploy user. A failed open can leave you with an empty or stale file. Write to a temp path and rename() for atomic replacement if readers may fetch during generation.
  • Gzip needs ext-zlib. Name the file .xml.gz and reference that name in the index. Don't serve gzipped bytes under a .xml name, and don't let nginx apply gzip again to an already-gzipped sitemap.
  • Only include indexable, canonical, 200-status URLs. Exclude redirects, 404s, noindex pages, authenticated areas, and faceted-filter permutations. A sitemap full of redirects is worse than no sitemap.
  • Add the sitemap to robots.txt. Sitemap: https://example.com/sitemaps/sitemap.xml — and submit the index, not every child sitemap, to Search Console.
  • Static files need cache invalidation. If a CDN or nginx caches /sitemap.xml, regenerating the origin file does nothing until the cache expires or is purged. Set a short TTL on sitemap paths.
  • There is no incremental update API. Every generation rewrites the full file. That's fine at nightly cadence; it is not suitable for real-time regeneration on every publish at scale.
  • Extensions are the main version-compatibility risk. The core addItem() API is stable; extension payload shapes have changed across releases. Pin the package version and re-read the extension source when upgrading.
  • For custom schemas, implement the package's extension interface rather than post-processing XML strings. The package owns namespace declarations and escaping; hand-editing output is where malformed sitemaps come from.
  • Validate before shipping. A schema validator or a quick xmllint --noout public/sitemaps/sitemap.xml in CI catches malformed output from bad slugs or timestamps far faster than Search Console does.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource