symkit / sitemap-bundle
A modern, high-performance Symfony bundle for XML sitemap generation
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/symkit/sitemap-bundle
Requires
- php: >=8.2
- rumenx/php-sitemap: ^1.0
- symfony/config: ^7.0 || ^8.0
- symfony/console: ^7.0 || ^8.0
- symfony/dependency-injection: ^7.0 || ^8.0
- symfony/event-dispatcher: ^7.0 || ^8.0
- symfony/http-foundation: ^7.0 || ^8.0
- symfony/http-kernel: ^7.0 || ^8.0
- symfony/routing: ^7.0 || ^8.0
- symfony/string: ^7.0 || ^8.0
Requires (Dev)
- deptrac/deptrac: ^2.0
- friendsofphp/php-cs-fixer: ^3.0
- infection/extension-installer: ^0.1
- infection/infection: ^0.29
- phpro/grumphp: ^2.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.0
- symfony/cache: ^7.0 || ^8.0
- symfony/framework-bundle: ^7.0 || ^8.0
- symfony/http-client: ^7.0 || ^8.0
- symkit/bundle-ai-kit: ^0.0.3
Suggests
- symfony/http-client: For IndexNow search engine notifications
- symfony/messenger: For async sitemap generation via Messenger
- symfony/twig-bundle: For Web Debug Toolbar profiler panel
- symfony/web-profiler-bundle: For Web Debug Toolbar profiler panel
README
A modern, high-performance Symfony bundle for XML sitemap generation.
Features
- Performant: Uses
iterableandyieldto handle millions of URLs with minimal memory footprint. - Automatic Chunking: Splits large sitemaps (default > 25,000 URLs) into paginated files.
- Pretty URLs: Clean URL structures like
/sitemap/pages-1.xml. - Image Support: Built-in support for Google Image Sitemaps.
- Video Support: Built-in support for Google Video Sitemaps.
- News Support: Built-in support for Google News Sitemaps.
- Multi-language (hreflang):
xhtml:linkalternates for multi-language sites. - Gzip Compression: Optional gzip compression for sitemap responses.
- Taggable Cache: Optimized caching with tag-based invalidation.
- Cache Warmup: Automatic cache warming via
cache:warmupor CLI command. - Console Commands:
sitemap:generate(warmup) andsitemap:debug(inspect loaders). - Profiler: Symfony Web Debug Toolbar integration with loader stats and generation timing.
- Async Generation: Optional Messenger-based async sitemap regeneration (disabled by default).
- IndexNow: Automatic search engine notification on sitemap invalidation.
- robots.txt: Customizable
robots.txtserving and automaticSitemap:directive injection. - Logging: PSR Logger integration on the
sitemapmonolog channel. - SOLID Architecture: Contract-based interfaces,
final readonlyservices, event-driven.
Installation
composer require symkit/sitemap-bundle
Routing
Import the bundle routes in your application:
# config/routes/symkit_sitemap.yaml symkit_sitemap: resource: '@SymkitSitemapBundle/config/routes.php'
This registers three routes:
| Route | Path | Description |
|---|---|---|
symkit_sitemap_index |
/sitemap.xml |
Sitemap index |
symkit_sitemap_show |
/sitemap/{name}.xml |
Single sitemap |
symkit_sitemap_show_paginated |
/sitemap/{name}-{page}.xml |
Paginated sitemap |
Configuration
# config/packages/symkit_sitemap.yaml symkit_sitemap: items_per_page: 25000 gzip: false robots_txt: enabled: false content: ~ # Full robots.txt content (serves /robots.txt when set) inject_sitemap: true # Auto-append Sitemap: directive cache: enabled: false pool: 'cache.app.taggable' tag: 'sitemap' ttl: 3600 messenger: enabled: false transport: 'async' index_now: enabled: false api_key: ~ # Your IndexNow API key host: ~ # Optional: your site hostname
| Parameter | Type | Default | Description |
|---|---|---|---|
items_per_page |
integer |
25000 |
Maximum URLs per sitemap chunk |
gzip |
boolean |
false |
Enable gzip compression for sitemap responses |
robots_txt.enabled |
boolean |
false |
Enable the robots.txt feature |
robots_txt.content |
string |
null |
Full robots.txt content — when set, the bundle serves /robots.txt with this content |
robots_txt.inject_sitemap |
boolean |
true |
Auto-append Sitemap: directive to robots.txt responses |
cache.enabled |
boolean |
false |
Enable caching for generated sitemaps |
cache.pool |
string |
cache.app.taggable |
Symfony cache pool (must support tagging) |
cache.tag |
string |
sitemap |
Cache tag for sitemap entries |
cache.ttl |
integer |
3600 |
Cache TTL in seconds |
messenger.enabled |
boolean |
false |
Enable async sitemap generation via Messenger |
messenger.transport |
string |
async |
Messenger transport name |
index_now.enabled |
boolean |
false |
Enable IndexNow notifications on invalidation |
index_now.api_key |
string |
null |
IndexNow API key |
index_now.host |
string |
null |
Site hostname for IndexNow |
Usage
1. Create a Loader
Implement SitemapLoaderInterface. With autoconfiguration enabled, the bundle detects it automatically:
namespace App\Sitemap; use Symkit\SitemapBundle\Contract\SitemapLoaderInterface; use Symkit\SitemapBundle\Model\SitemapUrl; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; final readonly class ProductSitemapLoader implements SitemapLoaderInterface { public function __construct( private ProductRepository $repository, private UrlGeneratorInterface $urlGenerator, ) { } public function count(): int { return $this->repository->countAllActive(); } public function load(int $limit, int $offset): iterable { foreach ($this->repository->findBy([], null, $limit, $offset) as $product) { yield new SitemapUrl( loc: $this->urlGenerator->generate( 'app_product_show', ['slug' => $product->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL, ), lastmod: $product->getUpdatedAt(), changefreq: 'daily', priority: '0.8', ); } } }
2. Multi-language (hreflang)
use Symkit\SitemapBundle\Model\SitemapAlternate; use Symkit\SitemapBundle\Model\SitemapUrl; yield new SitemapUrl( loc: 'https://example.com/en/page', alternates: [ new SitemapAlternate('fr', 'https://example.com/fr/page'), new SitemapAlternate('de', 'https://example.com/de/page'), new SitemapAlternate('x-default', 'https://example.com/en/page'), ], );
3. Video Sitemaps
use Symkit\SitemapBundle\Model\SitemapUrl; use Symkit\SitemapBundle\Model\SitemapVideo; yield new SitemapUrl( loc: 'https://example.com/videos/my-video', videos: [ new SitemapVideo( thumbnailLoc: 'https://example.com/thumbs/my-video.jpg', title: 'My Video Title', description: 'A description of the video.', contentLoc: 'https://example.com/videos/my-video.mp4', duration: '600', ), ], );
4. News Sitemaps
use Symkit\SitemapBundle\Model\SitemapNews; use Symkit\SitemapBundle\Model\SitemapUrl; yield new SitemapUrl( loc: 'https://example.com/news/breaking-story', news: new SitemapNews( publicationName: 'Example Times', publicationLanguage: 'en', title: 'Breaking Story Title', publicationDate: new \DateTimeImmutable(), ), );
5. Cache Invalidation
Dispatch SitemapInvalidateEvent or call SitemapCacheManagerInterface::invalidate():
use Symkit\SitemapBundle\Contract\SitemapCacheManagerInterface; final readonly class ProductEventSubscriber { public function __construct( private SitemapCacheManagerInterface $cacheManager, ) { } public function onProductUpdate(): void { $this->cacheManager->invalidate(); } }
6. Custom Loader Name
By default, the sitemap name is the snake_case version of your loader class name. Override it with the tag index attribute:
services: App\Sitemap\ProductSitemapLoader: tags: - { name: 'symkit_sitemap.loader', index: 'products' }
Console Commands
sitemap:generate
Generate and warm up all sitemap caches:
php bin/console sitemap:generate php bin/console sitemap:generate --name=products
sitemap:debug
Display registered loaders with URL counts and page numbers:
php bin/console sitemap:debug
Events
| Event | When |
|---|---|
SitemapPreGenerateEvent |
Before sitemap XML generation |
SitemapPostGenerateEvent |
After sitemap XML generation (contains XML) |
SitemapInvalidateEvent |
When cache invalidation is triggered |
Contributing
# Install dependencies make install # Install git hooks (strips Co-authored-by from commits) make install-hooks # Run full quality pipeline make quality
Available targets: make cs-fix, make phpstan, make test, make deptrac, make infection, make quality, make ci.
License
MIT