Skip to content

XML sitemap ​

The package serves a sitemap at /sitemap.xml (path configurable). URLs come from providers — small classes you write, one per content area.

A sitemap entry ​

SitemapEntry describes one URL:

php
use BasekitLaravel\BasekitLaravelSeo\Support\SitemapEntry;

new SitemapEntry(
    loc: 'https://acme.test/about',
    lastmod: '2026-09-21T10:30:00+00:00', // optional; ISO-8601
    changefreq: 'monthly',                // optional; see table below
    priority: 0.8,                        // optional; 0.0 – 1.0
);

Supported changefreq values: always, hourly, daily, weekly, monthly, yearly, never.

Writing a provider ​

Return an iterable of SitemapEntry objects:

php
use BasekitLaravel\BasekitLaravelSeo\Contracts\SitemapProvider;
use BasekitLaravel\BasekitLaravelSeo\Support\SitemapEntry;

final class ProductSitemapProvider implements SitemapProvider
{
    public function entries(): iterable
    {
        foreach (Product::where('visible', true)->latest()->get() as $product) {
            yield new SitemapEntry(
                loc: route('products.show', $product),
                lastmod: $product->updated_at?->toIso8601String(),
                changefreq: 'weekly',
            );
        }
    }
}

Registering providers ​

Tag each provider in a service provider; registration order is the order URLs appear in the sitemap:

php
use App\Seo\ProductSitemapProvider;
use App\Seo\PageSitemapProvider;
use BasekitLaravel\BasekitLaravelSeo\Contracts\SitemapProvider;

public function register(): void
{
    $this->app->tag(
        [ProductSitemapProvider::class, PageSitemapProvider::class],
        SitemapProvider::PROVIDER_TAG,
    );
}

Splitting and errors ​

  • Large sites are split automatically. When the output exceeds sitemap.max_urls (50,000) or sitemap.max_bytes (50 MiB), it is split at entry boundaries. /sitemap.xml becomes a sitemap index and the documents are served at /sitemap-1.xml, /sitemap-2.xml, ...
  • Duplicate URLs are dropped (first occurrence wins).
  • Errors surface. A provider that throws, or yields something that is not a SitemapEntry, fails the request — no partial sitemap is served. An entry too large for a single document raises InvalidArgumentException.
  • Missing chunks return 404. Asking for /sitemap-99.xml when only two documents were generated gives a 404, not an empty document.
  • A split sitemap needs a trusted origin. The <sitemapindex> references chunk URLs with absolute locations, so with no canonical.base_url, no app.url and an untrusted request host the package refuses to invent one and fails with an explanatory message. Configure canonical.base_url (or trusted_hosts) for any site large enough to be split. A single unsplit document is unaffected — it only contains provider-supplied locations.

Caching ​

Aggregation and rendering are cached so providers do not run on every request:

ConfigDefaultMeaning
sitemap.cache.enabledtrueMaster switch.
sitemap.cache.ttl3600Seconds the catalog and documents are kept. Also the HTTP max-age.
sitemap.cache.storenullCache store; null = default store.

Concurrent requests that arrive on a cold cache take a short-lived lock so only one rebuilds the sitemap; the others serve the freshly written document. For long-lived documents it is usually better to warm the cache after a deploy:

bash
php artisan basekit-seo:sitemap:warm

After content changes, invalidate the cache:

php
use BasekitLaravel\BasekitLaravelSeo\Services\SitemapCache;

app(SitemapCache::class)->clear();

Or from the command line:

bash
php artisan basekit-seo:sitemap:clear

Documents are keyed on their rendered content rather than on the request, and each one is served with an ETag, Cache-Control and 304 Not Modified support — so a CDN can cache them without hitting your application. See HTTP caching and Console commands.