Skip to content

Commit 117fe48

Browse files
committed
feat(docs): generate sitemap dynamically using build entry point
Replace the static sitemap.xml with dynamic generation via the new `build` entry point. The sitemap is now derived from the route definitions, ensuring it stays in sync as pages are added or removed. Also adds `outDir` to `BuildEntryContext` so build entries know where to write additional output files. https://claude.ai/code/session_01D7p4ghgU7hqJA6SnMfVtPA
1 parent 20f7df5 commit 117fe48

5 files changed

Lines changed: 50 additions & 49 deletions

File tree

packages/docs/public/sitemap.xml

Lines changed: 0 additions & 48 deletions
This file was deleted.

packages/docs/src/build.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { writeFile } from "node:fs/promises";
2+
import path from "node:path";
3+
import type { BuildEntryFunction } from "@funstack/static/build-entry";
4+
import type { RouteDefinition } from "@funstack/router/server";
5+
import { routes } from "./App";
6+
7+
const siteUrl = "https://static.funstack.work";
8+
9+
function collectPaths(routes: RouteDefinition[]): string[] {
10+
const paths: string[] = [];
11+
for (const route of routes) {
12+
if (route.children) {
13+
paths.push(...collectPaths(route.children));
14+
} else if (route.path !== undefined && route.path !== "*") {
15+
paths.push(route.path);
16+
}
17+
}
18+
return paths;
19+
}
20+
21+
function generateSitemap(paths: string[]): string {
22+
const urls = paths
23+
.map((p) => {
24+
const loc = p === "/" ? siteUrl + "/" : `${siteUrl}${p}`;
25+
return ` <url>\n <loc>${loc}</loc>\n </url>`;
26+
})
27+
.join("\n");
28+
29+
return `<?xml version="1.0" encoding="UTF-8"?>
30+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
31+
${urls}
32+
</urlset>
33+
`;
34+
}
35+
36+
export default (async ({ build, outDir }) => {
37+
const paths = collectPaths(routes);
38+
39+
await Promise.all([
40+
build(),
41+
writeFile(path.join(outDir, "sitemap.xml"), generateSitemap(paths)),
42+
]);
43+
}) satisfies BuildEntryFunction;

packages/docs/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default defineConfig(async () => {
1414
funstackStatic({
1515
entries: "./src/entries.tsx",
1616
ssr: true,
17+
build: "./src/build.ts",
1718
}),
1819
{
1920
// to make .mdx loading lazy

packages/static/src/build/buildApp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export async function buildApp(
7979
}
8080

8181
if (entry.buildEntry) {
82-
await entry.buildEntry({ build: doBuild });
82+
await entry.buildEntry({ build: doBuild, outDir: baseDir });
8383
} else {
8484
await doBuild();
8585
}

packages/static/src/buildEntryDefinition.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export interface BuildEntryContext {
88
* You can run additional work before, after, or in parallel with this function.
99
*/
1010
build: () => Promise<void>;
11+
/**
12+
* Absolute path to the output directory where built files are written.
13+
* Use this to write additional files (e.g. sitemap.xml) alongside the build output.
14+
*/
15+
outDir: string;
1116
}
1217

1318
/**

0 commit comments

Comments
 (0)