kunstmaan/sitemap-bundle
Symfony bundle for generating XML sitemaps for Kunstmaan CMS sites. It integrates with Kunstmaan page/node structures, provides sitemap generation and routing, and helps expose search-engine-friendly sitemap URLs for your application.
First, a reality check. kunstmaan/sitemap-bundle is a Symfony bundle, not a Laravel package. It targets the Kunstmaan CMS stack (kunstmaan/node-bundle, Doctrine ORM, Symfony 2.x-era DI), was last released in 2014-04-05, has 10 stars and zero dependents. There is no service provider, no facade, and no Laravel integration surface. Installing it into a Laravel app will not get you a working sitemap.xml; it will get you a bundle whose kernel/DI/config expectations Laravel does not satisfy.
So "getting started" splits into two paths:
Path A — you're actually on a Kunstmaan/Symfony site (the intended use case):
composer require kunstmaan/sitemap-bundle (pin to your Symfony version; expect old constraint conflicts).AppKernel.php, and confirm the node bundle it depends on is present — the generators enumerate Node entities.SitemapController (typically /sitemap.xml) and check the config for the cache directory used to store the generated file.kuma:sitemap:generate) and point your web server at the cached file, or serve the dynamic controller response.Path B — you're a Laravel developer evaluating this for a Laravel project (the realistic one):
addUrl($url, $priority, $changeFreq) into a shared collection, a service that aggregates all registered generators, and a command/route that renders the collection to XML.spatie/laravel-sitemap and write custom generators that mirror the Kunstmaan pattern (one generator per content type, registered in a service provider).<url> per published, non-hidden, localized node with a canonical absolute URL." That is 90% of what this bundle does for a Kunstmaan site.Custom generator (the extension point that matters). The bundle's contract is: subclass the abstract sitemap generator, implement your getUrls()-style logic, and tag the service so the aggregator picks it up (the tag is kunstmaan_sitemap.sitemap_generator in the bundle's DI convention). Inside the generator you call the protected addUrl() helper; you never build XML yourself.
// Symfony/Kunstmaan — the intended pattern
class ArticleSitemapGenerator extends AbstractSitemapGenerator
{
public function __construct(private EntityManagerInterface $em) {}
public function getUrls(): array
{
foreach ($this->em->getRepository(Article::class)->findPublished() as $article) {
$this->addUrl($article->getAbsoluteUrl(), 0.8, 'weekly');
}
return parent::getUrls();
}
}
Aggregation and rendering. The central sitemap service iterates every registered generator in order, concatenates their URLs, and either renders a template to XML on the fly or writes it to the configured cache file. Keep generators side-effect free and deterministic — ordering of output is registration order, which is fragile, so don't rely on it for anything meaningful.
Static file + cron is the production pattern. Because sitemaps change slowly, the common workflow is: scheduled command regenerates the cached XML (hourly/nightly), a web-server rule or route serves that static file, and sitemap.xml never touches PHP per request. This mirrors what you'd do in Laravel with Schedule::command('sitemap:generate')->hourly() writing to public/sitemap.xml.
Laravel translation of the pattern. Keep the same shape but use Laravel idioms:
// app/Sitemap/SitemapGenerator.php
interface SitemapGenerator { public function urls(): iterable; }
Register implementations in a dedicated SitemapServiceProvider, tag them in a container binding ($this->app->tag([...], 'sitemap.generator')), and resolve them with $this->app->tagged('sitemap.generator') in a GenerateSitemap action class. Then a SitemapController or the spatie/laravel-sitemap builder renders the collection. The result is the same architecture with Symfony-era baggage removed.
Localization / multi-language sites. Kunstmaan nodes are localized, so the bundle's generators typically emit one URL per active locale. If you port this, do the locale loop inside each generator (or in a decorator) so you don't scatter ?lang= logic across controllers. Watch for duplicate canonical URLs across locales — dedupe on (path, locale default).
Sitemap index for large sites. Neither the bundle nor a naive port enforces the 50,000-URL / 50 MB-per-file limits. If you're above a few thousand URLs, wrap generators so each content type produces its own file and emit a <sitemapindex> — do this in the aggregator, not in individual generators.
www-data and the cron user, and log the resolved absolute path on startup.iterate() / batch) rather than findAll().addUrl() does not guarantee escaping or absolutization. Pass fully-qualified https:// URLs with no trailing-slash ambiguity, and run the output through a sanity check — one bad & in a slug breaks the whole document.priority and changeFreq are largely ignored by Google. Don't spend design time on them; correctness of <loc> and lastmod matters far more.debug:container --tag=...); (2) does the generator filter out unpublished/hidden nodes; (3) is the sitemap being served from a stale cache file rather than regenerated. That third one wastes the most time; always regenerate before debugging generation logic.addUrl helper). There is no meaningful event system — don't go looking for one.Content-Type: application/xml; charset=utf-8, and consider gzip at the web-server layer rather than in PHP. If you serve the dynamic controller instead of a static file, make sure caching headers don't pin a stale sitemap forever.sitemap.xml will not catch the unescaped ampersand or the duplicate locale entries.How can I help you explore Laravel packages today?