Skip to content

Commit 61b1f51

Browse files
authored
Add sections to llms.txt and sitemap footer to *.md (#8270)
* Add sections to llms.txt * Also add sitemap footer
1 parent dcc5deb commit 61b1f51

File tree

2 files changed

+211
-19
lines changed

2 files changed

+211
-19
lines changed

src/pages/api/md/[...path].ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import type {NextApiRequest, NextApiResponse} from 'next';
99
import fs from 'fs';
1010
import path from 'path';
1111

12+
const FOOTER = `
13+
---
14+
15+
## Sitemap
16+
17+
[Overview of all docs pages](/llms.txt)
18+
`;
19+
1220
export default function handler(req: NextApiRequest, res: NextApiResponse) {
1321
const pathSegments = req.query.path;
1422
if (!pathSegments) {
@@ -35,7 +43,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
3543
const content = fs.readFileSync(fullPath, 'utf8');
3644
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
3745
res.setHeader('Cache-Control', 'public, max-age=3600');
38-
return res.status(200).send(content);
46+
return res.status(200).send(content + FOOTER);
3947
} catch {
4048
// Try next candidate
4149
}

src/pages/llms.txt.tsx

Lines changed: 202 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,45 +9,195 @@ import type {GetServerSideProps} from 'next';
99
import {siteConfig} from '../siteConfig';
1010
import sidebarLearn from '../sidebarLearn.json';
1111
import sidebarReference from '../sidebarReference.json';
12-
import sidebarBlog from '../sidebarBlog.json';
1312

1413
interface RouteItem {
1514
title?: string;
1615
path?: string;
1716
routes?: RouteItem[];
17+
hasSectionHeader?: boolean;
18+
sectionHeader?: string;
1819
}
1920

2021
interface Sidebar {
2122
title: string;
2223
routes: RouteItem[];
2324
}
2425

25-
function extractRoutes(
26+
interface Page {
27+
title: string;
28+
url: string;
29+
}
30+
31+
interface SubGroup {
32+
heading: string;
33+
pages: Page[];
34+
}
35+
36+
interface Section {
37+
heading: string | null;
38+
pages: Page[];
39+
subGroups: SubGroup[];
40+
}
41+
42+
// Clean up section header names (remove version placeholders)
43+
function cleanSectionHeader(header: string): string {
44+
return header
45+
.replace(/@\{\{version\}\}/g, '')
46+
.replace(/-/g, ' ')
47+
.split(' ')
48+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
49+
.join(' ')
50+
.trim();
51+
}
52+
53+
// Extract routes for sidebars that use hasSectionHeader to define major sections
54+
// (like the API Reference sidebar)
55+
function extractSectionedRoutes(
2656
routes: RouteItem[],
2757
baseUrl: string
28-
): {title: string; url: string}[] {
29-
const result: {title: string; url: string}[] = [];
58+
): Section[] {
59+
const sections: Section[] = [];
60+
let currentSection: Section | null = null;
3061

3162
for (const route of routes) {
32-
if (route.title && route.path) {
33-
result.push({
63+
// Skip external links
64+
if (route.path?.startsWith('http')) {
65+
continue;
66+
}
67+
68+
// Start a new section when we hit a section header
69+
if (route.hasSectionHeader && route.sectionHeader) {
70+
if (currentSection) {
71+
sections.push(currentSection);
72+
}
73+
currentSection = {
74+
heading: cleanSectionHeader(route.sectionHeader),
75+
pages: [],
76+
subGroups: [],
77+
};
78+
continue;
79+
}
80+
81+
// If no section started yet, skip
82+
if (!currentSection) {
83+
continue;
84+
}
85+
86+
// Route with children - create a sub-group
87+
if (route.title && route.routes && route.routes.length > 0) {
88+
const subGroup: SubGroup = {
89+
heading: route.title,
90+
pages: [],
91+
};
92+
93+
// Include parent page if it has a path
94+
if (route.path) {
95+
subGroup.pages.push({
96+
title: route.title,
97+
url: `${baseUrl}${route.path}.md`,
98+
});
99+
}
100+
101+
// Add child pages
102+
for (const child of route.routes) {
103+
if (child.title && child.path && !child.path.startsWith('http')) {
104+
subGroup.pages.push({
105+
title: child.title,
106+
url: `${baseUrl}${child.path}.md`,
107+
});
108+
}
109+
}
110+
111+
if (subGroup.pages.length > 0) {
112+
currentSection.subGroups.push(subGroup);
113+
}
114+
}
115+
// Single page without children
116+
else if (route.title && route.path) {
117+
currentSection.pages.push({
34118
title: route.title,
35119
url: `${baseUrl}${route.path}.md`,
36120
});
37121
}
38-
if (route.routes) {
39-
result.push(...extractRoutes(route.routes, baseUrl));
122+
}
123+
124+
// Don't forget the last section
125+
if (currentSection) {
126+
sections.push(currentSection);
127+
}
128+
129+
return sections;
130+
}
131+
132+
// Extract routes for sidebars that use routes with children as the primary grouping
133+
// (like the Learn sidebar)
134+
function extractGroupedRoutes(
135+
routes: RouteItem[],
136+
baseUrl: string
137+
): SubGroup[] {
138+
const groups: SubGroup[] = [];
139+
140+
for (const route of routes) {
141+
// Skip section headers
142+
if (route.hasSectionHeader) {
143+
continue;
144+
}
145+
146+
// Skip external links
147+
if (route.path?.startsWith('http')) {
148+
continue;
149+
}
150+
151+
// Route with children - create a group
152+
if (route.title && route.routes && route.routes.length > 0) {
153+
const pages: Page[] = [];
154+
155+
// Include parent page if it has a path
156+
if (route.path) {
157+
pages.push({
158+
title: route.title,
159+
url: `${baseUrl}${route.path}.md`,
160+
});
161+
}
162+
163+
// Add child pages
164+
for (const child of route.routes) {
165+
if (child.title && child.path && !child.path.startsWith('http')) {
166+
pages.push({
167+
title: child.title,
168+
url: `${baseUrl}${child.path}.md`,
169+
});
170+
}
171+
}
172+
173+
if (pages.length > 0) {
174+
groups.push({
175+
heading: route.title,
176+
pages,
177+
});
178+
}
179+
}
180+
// Single page without children - group under its own heading
181+
else if (route.title && route.path) {
182+
groups.push({
183+
heading: route.title,
184+
pages: [
185+
{
186+
title: route.title,
187+
url: `${baseUrl}${route.path}.md`,
188+
},
189+
],
190+
});
40191
}
41192
}
42193

43-
return result;
194+
return groups;
44195
}
45196

46-
const sidebars: Sidebar[] = [
47-
sidebarLearn as Sidebar,
48-
sidebarReference as Sidebar,
49-
sidebarBlog as Sidebar,
50-
];
197+
// Check if sidebar uses section headers as primary grouping
198+
function usesSectionHeaders(routes: RouteItem[]): boolean {
199+
return routes.some((r) => r.hasSectionHeader && r.sectionHeader);
200+
}
51201

52202
export const getServerSideProps: GetServerSideProps = async ({res}) => {
53203
const subdomain =
@@ -60,14 +210,48 @@ export const getServerSideProps: GetServerSideProps = async ({res}) => {
60210
'> The library for web and native user interfaces.',
61211
];
62212

213+
const sidebars: Sidebar[] = [
214+
sidebarLearn as Sidebar,
215+
sidebarReference as Sidebar,
216+
];
217+
63218
for (const sidebar of sidebars) {
64219
lines.push('');
65220
lines.push(`## ${sidebar.title}`);
66-
lines.push('');
67221

68-
const routes = extractRoutes(sidebar.routes, baseUrl);
69-
for (const route of routes) {
70-
lines.push(`- [${route.title}](${route.url})`);
222+
if (usesSectionHeaders(sidebar.routes)) {
223+
// API Reference style: section headers define major groups
224+
const sections = extractSectionedRoutes(sidebar.routes, baseUrl);
225+
for (const section of sections) {
226+
if (section.heading) {
227+
lines.push('');
228+
lines.push(`### ${section.heading}`);
229+
}
230+
231+
// Output pages directly under section
232+
for (const page of section.pages) {
233+
lines.push(`- [${page.title}](${page.url})`);
234+
}
235+
236+
// Output sub-groups with #### headings
237+
for (const subGroup of section.subGroups) {
238+
lines.push('');
239+
lines.push(`#### ${subGroup.heading}`);
240+
for (const page of subGroup.pages) {
241+
lines.push(`- [${page.title}](${page.url})`);
242+
}
243+
}
244+
}
245+
} else {
246+
// Learn style: routes with children define groups
247+
const groups = extractGroupedRoutes(sidebar.routes, baseUrl);
248+
for (const group of groups) {
249+
lines.push('');
250+
lines.push(`### ${group.heading}`);
251+
for (const page of group.pages) {
252+
lines.push(`- [${page.title}](${page.url})`);
253+
}
254+
}
71255
}
72256
}
73257

0 commit comments

Comments
 (0)