Skip to content

Commit b567121

Browse files
committed
Add sitemaps generation as well, for DocsBot
1 parent 60e9364 commit b567121

4 files changed

Lines changed: 200 additions & 1 deletion

File tree

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,32 @@ Fetch GravityKit hooks from https://www.gravitykit.dev/api/hooks/{product}.json
332332

333333
**Products**: `gravityview`, `gravitycalendar`, `gravitycharts`, `gravityedit`, `gravityexport`, `gravityimport`, `gravitymath`, `gravityactions`, `gravityboard`, `gravitymigrate`, `gravityrevisions`, and more.
334334

335+
## Sitemaps
336+
337+
The site automatically generates XML sitemaps for search engine optimization:
338+
339+
- **`/sitemap.xml`** - Main site-wide sitemap (all pages)
340+
- **`/sitemap-products.xml`** - Product-specific sitemap index
341+
- **`/docs/{product}/sitemap.xml`** - Individual product sitemaps (28 products)
342+
343+
### Features
344+
345+
- **Automatic Generation**: Sitemaps are built during `npm run build`
346+
- **Smart Priorities**: Product home pages (0.9), top-level docs (0.7), hooks (0.6)
347+
- **HTML Discovery**: Both sitemaps are linked in the `<head>` of every page
348+
- **robots.txt**: Both sitemaps are listed for search engine crawlers
349+
350+
### Example URLs
351+
352+
```
353+
https://www.gravitykit.dev/sitemap.xml
354+
https://www.gravitykit.dev/sitemap-products.xml
355+
https://www.gravitykit.dev/docs/gravityview/sitemap.xml
356+
https://www.gravitykit.dev/docs/gravitycalendar/sitemap.xml
357+
```
358+
359+
For more details, see [SITEMAPS.md](./SITEMAPS.md).
360+
335361
## Environment Variables
336362

337363
The site uses environment variables for optional integrations. Set these in your deployment environment or local `.env` file.

docusaurus.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ const markdown_endpoints_plugin = fileURLToPath(
150150
new URL('./src/plugins/markdown-endpoints.mjs', import.meta.url),
151151
);
152152

153+
// Product sitemaps plugin - generates per-product sitemap.xml files
154+
const product_sitemaps_plugin = [
155+
fileURLToPath(new URL('./src/plugins/product-sitemaps.mjs', import.meta.url)),
156+
{
157+
products: products_with_docs,
158+
},
159+
];
160+
153161
/** @type {import('@docusaurus/types').Config} */
154162
const config = {
155163
title: 'GravityKit Developer Documentation',
@@ -189,6 +197,14 @@ const config = {
189197
href: normalizeUrl([site_url, base_url, 'sitemap.xml']),
190198
},
191199
},
200+
{
201+
tagName: 'link',
202+
attributes: {
203+
rel: 'sitemap',
204+
type: 'application/xml',
205+
href: normalizeUrl([site_url, base_url, 'sitemap-products.xml']),
206+
},
207+
},
192208
],
193209

194210
// Even if you don't use internationalization, you can use this field to set
@@ -342,6 +358,7 @@ const config = {
342358
product_llms_plugin,
343359
...llms_static_plugin,
344360
markdown_endpoints_plugin,
361+
product_sitemaps_plugin,
345362
].filter((pluginEntry) => {
346363
if (!Array.isArray(pluginEntry) || pluginEntry[0] !== '@docusaurus/plugin-content-docs') {
347364
return true;

src/plugins/product-sitemaps.mjs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* Docusaurus plugin to generate per-product sitemaps
3+
*
4+
* This plugin hooks into the build lifecycle and generates individual
5+
* sitemap.xml files for each product documentation section.
6+
*/
7+
8+
import fs from 'node:fs/promises';
9+
import path from 'node:path';
10+
import { normalizeUrl } from '@docusaurus/utils';
11+
12+
/**
13+
* Generate XML sitemap content
14+
*/
15+
function generateSitemapXML(urls, productLabel) {
16+
const urlEntries = urls
17+
.map(item => {
18+
return ` <url>
19+
<loc>${item.url}</loc>
20+
<lastmod>${item.lastmod || new Date().toISOString().split('T')[0]}</lastmod>
21+
<changefreq>${item.changefreq || 'weekly'}</changefreq>
22+
<priority>${item.priority || 0.5}</priority>
23+
</url>`;
24+
})
25+
.join('\n');
26+
27+
return `<?xml version="1.0" encoding="UTF-8"?>
28+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
29+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
30+
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
31+
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
32+
<!-- Generated: ${new Date().toISOString()} -->
33+
<!-- Product: ${productLabel} -->
34+
<!-- Total URLs: ${urls.length} -->
35+
${urlEntries}
36+
</urlset>
37+
`;
38+
}
39+
40+
/**
41+
* Generate a sitemap index file
42+
*/
43+
function generateSitemapIndex(products, baseUrl) {
44+
const sitemaps = products
45+
.map(product => {
46+
return ` <sitemap>
47+
<loc>${normalizeUrl([baseUrl, 'docs', product.id, 'sitemap.xml'])}</loc>
48+
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
49+
</sitemap>`;
50+
})
51+
.join('\n');
52+
53+
return `<?xml version="1.0" encoding="UTF-8"?>
54+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
55+
<!-- Generated: ${new Date().toISOString()} -->
56+
<!-- Total Product Sitemaps: ${products.length} -->
57+
${sitemaps}
58+
</sitemapindex>
59+
`;
60+
}
61+
62+
/**
63+
* Product Sitemaps Plugin
64+
*
65+
* @param {import('@docusaurus/types').LoadContext} context
66+
* @param {object} options
67+
* @returns {import('@docusaurus/types').Plugin}
68+
*/
69+
export default function productSitemapsPlugin(context, options) {
70+
const { siteConfig, generatedFilesDir } = context;
71+
const { products = [] } = options;
72+
73+
return {
74+
name: 'product-sitemaps',
75+
76+
async postBuild({ routesPaths, outDir, routes }) {
77+
if (!products || products.length === 0) {
78+
return;
79+
}
80+
81+
const baseUrl = normalizeUrl([siteConfig.url, siteConfig.baseUrl]);
82+
const productsWithSitemaps = [];
83+
84+
console.log('\n🗺️ Generating per-product sitemaps...');
85+
86+
// Generate sitemap for each product
87+
for (const product of products) {
88+
const productId = product.id;
89+
const productLabel = product.label;
90+
91+
// Filter routes that belong to this product
92+
const productRoutes = routesPaths.filter(route => {
93+
// Match routes like /docs/{productId}/ or /docs/{productId}/...
94+
return route.startsWith(`/docs/${productId}/`) || route === `/docs/${productId}`;
95+
});
96+
97+
if (productRoutes.length === 0) {
98+
console.log(` ⚠️ Skipping ${productId} - no routes found`);
99+
continue;
100+
}
101+
102+
// Convert routes to sitemap items
103+
const sitemapItems = productRoutes.map(route => {
104+
const url = normalizeUrl([baseUrl, route]);
105+
106+
// Determine priority based on route depth
107+
let priority = 0.5;
108+
let changefreq = 'monthly';
109+
110+
if (route === `/docs/${productId}` || route === `/docs/${productId}/`) {
111+
// Product home page
112+
priority = 0.9;
113+
changefreq = 'weekly';
114+
} else if (route.match(/\/docs\/[^/]+\/[^/]+\/?$/)) {
115+
// Top-level pages (one level deep)
116+
priority = 0.7;
117+
changefreq = 'weekly';
118+
} else if (route.includes('/actions/') || route.includes('/filters/')) {
119+
// Hook documentation
120+
priority = 0.6;
121+
changefreq = 'monthly';
122+
}
123+
124+
return {
125+
url,
126+
lastmod: new Date().toISOString().split('T')[0],
127+
changefreq,
128+
priority,
129+
};
130+
});
131+
132+
// Generate sitemap XML
133+
const sitemapXML = generateSitemapXML(sitemapItems, productLabel);
134+
135+
// Write to output directory at /docs/{productId}/sitemap.xml
136+
const sitemapPath = path.join(outDir, 'docs', productId, 'sitemap.xml');
137+
await fs.mkdir(path.dirname(sitemapPath), { recursive: true });
138+
await fs.writeFile(sitemapPath, sitemapXML, 'utf-8');
139+
140+
console.log(` ✅ Generated ${productId} sitemap (${sitemapItems.length} URLs)`);
141+
productsWithSitemaps.push(product);
142+
}
143+
144+
// Generate sitemap index
145+
if (productsWithSitemaps.length > 0) {
146+
const sitemapIndex = generateSitemapIndex(productsWithSitemaps, baseUrl);
147+
const indexPath = path.join(outDir, 'sitemap-products.xml');
148+
await fs.writeFile(indexPath, sitemapIndex, 'utf-8');
149+
console.log(` ✅ Generated sitemap index (${productsWithSitemaps.length} products)`);
150+
}
151+
152+
console.log('✅ Product sitemaps generation complete\n');
153+
},
154+
};
155+
}

static/robots.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
User-agent: *
55
Allow: /
66

7-
# Sitemap location
7+
# Sitemap locations
88
Sitemap: https://www.gravitykit.dev/sitemap.xml
9+
Sitemap: https://www.gravitykit.dev/sitemap-products.xml
910

1011
# Disallow build artifacts and internal paths
1112
Disallow: /assets/js/*.js.map

0 commit comments

Comments
 (0)