Generate XML sitemaps automatically from your TanStack Router route definitions. This plugin integrates seamlessly with TanStack Router to create SEO-friendly sitemaps without manual configuration. It supports Tanstack Router and Tanstack Start!
- π Automatic sitemap generation from TanStack Router routes
- π§ Flexible configuration options for different use cases
- π― Smart route filtering - excludes dynamic routes by default
- π Customizable metadata per route (priority, changefreq, lastmod)
- π SEO optimized XML output following sitemap.org standards
- β‘ Vite plugin support for seamless integration
- π TypeScript support with full type definitions
- π Manual route generation for dynamic content (blog posts, articles, etc.)
npm install @corentints/tanstack-router-sitemap
Add the plugin to your vite.config.ts
:
import { defineConfig } from 'vite';
import { sitemapPlugin } from '@corentints/tanstack-router-sitemap';
export default defineConfig({
plugins: [
// ... your other plugins
sitemapPlugin({
baseUrl: 'https://your-domain.com',
outputPath: 'public/sitemap.xml',
}),
],
});
Add the plugin to your app.config.ts
:
export default defineConfig({
// ...
vite: {
plugins: [
// ... your other plugins
sitemapPlugin({
baseUrl: 'https://your-domain.com',
outputPath: "public/sitemap.xml",
verbose: true,
}),
],
},
// ...
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { join } from 'path';
import { generateSitemap } from '@corentints/tanstack-router-sitemap';
import { routeTree } from './routeTree.gen'; // Your generated route tree
const sitemap = await generateSitemap(routeTree, {
baseUrl: 'https://your-domain.com',
defaultChangefreq: 'weekly',
defaultPriority: 0.8,
});
// Ensure the public directory exists
const outputPath = 'public/sitemap.xml';
const outputDir = join(process.cwd(), 'public');
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true });
}
// Write sitemap to file
writeFileSync(join(process.cwd(), outputPath), sitemap, 'utf8');
console.log(`β
Sitemap saved to ${outputPath}`);
Option | Type | Default | Description |
---|---|---|---|
baseUrl |
string |
Required | Base URL for your site (e.g., 'https://example.com') |
defaultChangefreq |
string |
'weekly' |
Default changefreq for all routes |
defaultPriority |
number |
0.5 |
Default priority for all routes (0.0 to 1.0) |
excludeRoutes |
string[] |
[] |
Routes to exclude from sitemap (supports glob patterns) |
routeOptions |
Record<string, Partial<SitemapEntry>> |
{} |
Custom configurations per route |
trailingSlash |
boolean |
false |
Whether to include trailing slashes |
lastmod |
string |
Current date | Custom lastmod date for all routes |
prettyPrint |
boolean |
true |
Pretty print the XML output |
manualRoutes |
() => Promise<ManualSitemapEntry[]> | ManualSitemapEntry[] |
undefined |
Function to generate manual/dynamic routes |
Option | Type | Default | Description |
---|---|---|---|
outputPath |
string |
'public/sitemap.xml' |
Output path for the sitemap.xml file |
verbose |
boolean |
false |
Whether to log sitemap generation info |
routeTreePath |
string |
Auto-detected | Path to routeTree.gen.ts file |
onBuildOnly |
boolean |
false |
Only generate sitemap on build |
sitemapPlugin({
baseUrl: 'https://your-domain.com',
excludeRoutes: [
'/admin', // Exclude specific route
'/admin/*', // Exclude all admin routes
'/api/*', // Exclude all API routes
'/private-*', // Exclude routes starting with 'private-'
],
});
sitemapPlugin({
baseUrl: 'https://your-domain.com',
routeOptions: {
'/': {
priority: 1.0,
changefreq: 'daily',
},
'/blog': {
priority: 0.8,
changefreq: 'weekly',
},
'/about': {
priority: 0.6,
changefreq: 'monthly',
lastmod: '2024-01-01T00:00:00.000Z',
},
},
});
The plugin automatically:
- β
Includes static routes (e.g.,
/about
,/contact
) - β Excludes dynamic routes (e.g.,
/users/$id
,/posts/[slug]
) - β Excludes routes in your
excludeRoutes
configuration - β Processes nested route structures recursively
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://your-domain.com/</loc>
<lastmod>2024-01-15T12:00:00.000Z</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://your-domain.com/about</loc>
<lastmod>2024-01-15T12:00:00.000Z</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://your-domain.com/blog</loc>
<lastmod>2024-01-15T12:00:00.000Z</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
For dynamic routes like blog posts, articles, or other database-driven content, you can use the manualRoutes
option:
manualRoutes: async () => {
const [posts, products, events] = await Promise.all([
fetchBlogPosts(),
fetchProducts(),
fetchEvents()
]);
return [
...posts.map(post => ({
location: `/blog/${post.slug}`,
priority: 0.8,
lastMod: post.publishedAt,
changeFrequency: 'weekly' as const
})),
...products.map(product => ({
location: `/shop/${product.id}`,
priority: 0.7,
changeFrequency: 'monthly' as const
})),
...events.map(event => ({
location: `/events/${event.id}`,
priority: 0.9,
lastMod: event.updatedAt,
changeFrequency: 'daily' as const
}))
];
}
Contributions are welcome! Please feel free to submit a Pull Request.
Built with β€οΈ for the TanStack community