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

Symfony Sitemap Bundle Laravel Package

m-adamski/symfony-sitemap-bundle

Symfony bundle for generating XML sitemaps in your application. Integrates sitemap creation with Symfony's configuration and services, helping you expose URLs to search engines with a framework-native setup.

View on GitHub
Deep Wiki
Context7

Getting Started

The first thirty minutes should be spent reading source, not docs — this package has effectively no documentation beyond a one-line changelog, a 0.3 score, and a last release of June 2021.

  1. Gate the install on constraints. Before composer require m-adamski/symfony-sitemap-bundle, open the package's composer.json and confirm the declared php and symfony/* ranges against your runtime. A 2021-era bundle may not declare Symfony 6/7 support even if it happens to run. This is a five-minute check that decides whether the rest is worth doing.
  2. Register the bundle in config/bundles.php (or AppKernel on older Symfony), then drop into the source and read four files in this order:
    • src/DependencyInjection/Configuration.php — the authoritative list of config keys (base URL, providers, caching, output settings).
    • src/DependencyInjection/*Extension.php — which services are registered and which service tags wire providers into the generator.
    • src/Controller/ — how sitemap.xml is actually served and with what Content-Type.
    • src/Provider/ (or equivalent) — the interface your own providers must implement.
  3. First use case — serve a static sitemap. Point the bundle config at a base URL, register the built-in provider (if any) or one trivial provider returning two hard-coded routes, and confirm GET /sitemap.xml returns 200 with Content-Type: application/xml and parses cleanly:
curl -sI https://app.test/sitemap.xml
curl -s https://app.test/sitemap.xml | xmllint --noout -

Get that green before touching dynamic content. The bundle is thin; the failure surface is almost entirely "your provider or your config," so you want a known-good baseline first.

Framework note: if you are coming from Laravel, the mental model maps cleanly — bundle config extension ≈ a package config file, tagged services ≈ tagged bindings resolved by an interface, the controller ≈ a published route. The one Symfony-specific thing to learn is the tag name that registers a provider; that tag is the extension point for everything else.

Implementation Patterns

One provider per content type. Do not build a single god-provider. Split into StaticPagesProvider, BlogPostsProvider, ProductsProvider, each implementing the bundle's provider interface and tagged in config/services.yaml:

App\Sitemap\BlogPostsProvider:
    tags: ['<tag discovered in the bundle extension>']

This keeps each provider independently testable and lets you disable one (say, products during a migration) without touching the others.

Generate URLs through the router, never by string concatenation. Inside a provider, inject Symfony\Component\Routing\Generator\UrlGeneratorInterface and call generate($routeName, $params, UrlGeneratorInterface::ABSOLUTE_URL). This inherits your locale prefixes, host, scheme, and route-parameter encoding for free. Hand-built URLs are the source of 90% of malformed-sitemap bugs — wrong host, missing slash, & unescaped.

Keep providers read-only and bounded. A provider runs per sitemap request; a naive findAll() will OOM on any real dataset. Use repository methods with a cursor/iterate() (batch over IDs), and never trigger lazy-loaded associations per row — that is an N+1 hidden inside XML output. If the dataset can exceed 50,000 URLs, you must plan for a sitemap index (see Gotchas).

Cache the rendered output, not the provider calls. The bundle is described as cache-friendly; make it so at your layer. Wrap the generator in your own SitemapRenderer service that stores the final XML string in a Symfony cache pool under a content-version key:

$xml = $this->cache->get('sitemap_v' . $this->contentVersion, function (ItemInterface $item) {
    $item->expiresAfter(3600);
    return $this->inner->render();
});

Bump contentVersion on deploy or on a content-change event. This makes the sitemap a static file for 99.9% of requests and turns cache invalidation into a single knob you control.

Pre-generate on a schedule. For large or slow sitemaps, don't generate on request at all: add a console command (app:sitemap:warm) that renders and writes to cache or to public/sitemap.xml, and run it from cron or after deploy. The controller then just returns the cached string. This also gives you a place to log URL counts and diff them across deploys.

Test it as a functional endpoint. One WebTestCase asserting: status 200, Content-Type contains xml, document parses, expected canonical URLs are present, and no duplicate <loc> values. That last assertion is what catches providers accidentally registering overlapping route sets.

Gotchas and Tips

  • Treat the package as unmaintained until proven otherwise. Last release 2021-06-18, zero stars, one-line changelog. Pin the exact version in composer.json ("1.0.1", not "^1.0"), read the source diff of any future release, and evaluate during a spike whether vendoring the ~few-hundred lines in-house is cheaper than depending on it. For a bundle this small, forking is a realistic exit.
  • 1.0.1 added "additional check while generating alternates." That phrasing is the only release note you get. Treat it as defensive validation that may now silently skip entries that previously appeared. After upgrading, diff the generated XML before and after on your most parameterized routes; if a URL disappears, the new check is the first suspect. Add a debug log in your wrapper recording what each provider emitted versus what survived.
  • Absolute URLs and proxy context. Behind a load balancer, Symfony's router may generate http://internal-host/... unless you configure framework.router.request_context (or trusted proxies) with the public scheme and host. Sitemap URLs must be absolute and public-facing. This is the single most common production-only failure: fine locally, wrong host in staging.
  • Escape through a writer, not string interpolation. If the bundle builds XML via DOMDocument/XMLWriter you're fine; if any provider is expected to return raw markup, a URL containing & (query strings, filters) will produce invalid XML that browsers silently reject. Keep URLs parameter-free — a sitemap should never contain session IDs or tracking params.
  • The 50,000-URL / 50 MB limits are hard. If you're near them, split into multiple sitemaps plus a <sitemapindex>. Check whether the bundle supports an index out of the box; if not, generate the child maps with it and hand-write the index (it's ten lines of XML). Don't discover this at scale.
  • <lastmod> must be W3C datetime, not a timestamp or Y-m-d. If your provider passes DateTime, confirm the bundle formats it; otherwise format explicitly with ->format(DATE_W3C). Incorrect lastmod is ignored at best, distrusted at worst.
  • Cache keys are the debugging trap. After changing config, routes, or a provider, a stale sitemap is nearly always (a) Symfony's compiled container/config cache, (b) HTTP cache, or (c) your own render cache. When output looks wrong, first run bin/console cache:clear and hit the endpoint with a cache-busting header before you debug any code. Document this in your runbook.
  • Debug outside the HTTP stack. Write a throwaway console command that resolves the generator, iterates every configured provider, and prints URL counts plus a sample. Debugging sitemap logic through curl and a browser is slow and hides which provider is at fault.
  • Extension points, in order of preference: (1) register your own tagged providers — always safe; (2) decorate the bundle's generator/renderer service to add caching or filtering, which survives upgrades better than config contortions; (3) override the bundle's controller route in your own routing config if you need custom headers or auth. Editing anything under vendor/ is not a strategy, it's a scheduled outage.
  • Don't assume it emits hreflang alternates. The changelog's mention of "alternates" concerns generation, not multilingual SEO headers. If you need multilingual /en/, /fr/ cross-linking, verify in the XML output rather than assuming, and add <xhtml:link> yourself if absent.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package