Add sections to llms.txt and sitemap footer to *.md (#8270)

* Add sections to llms.txt

* Also add sitemap footer
This commit is contained in:
Ricky
2026-01-28 11:43:24 -05:00
committed by GitHub
parent dcc5deb2f7
commit 61b1f51283
2 changed files with 211 additions and 19 deletions

View File

@@ -9,6 +9,14 @@ import type {NextApiRequest, NextApiResponse} from 'next';
import fs from 'fs';
import path from 'path';
const FOOTER = `
---
## Sitemap
[Overview of all docs pages](/llms.txt)
`;
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const pathSegments = req.query.path;
if (!pathSegments) {
@@ -35,7 +43,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
const content = fs.readFileSync(fullPath, 'utf8');
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Cache-Control', 'public, max-age=3600');
return res.status(200).send(content);
return res.status(200).send(content + FOOTER);
} catch {
// Try next candidate
}

View File

@@ -9,12 +9,13 @@ import type {GetServerSideProps} from 'next';
import {siteConfig} from '../siteConfig';
import sidebarLearn from '../sidebarLearn.json';
import sidebarReference from '../sidebarReference.json';
import sidebarBlog from '../sidebarBlog.json';
interface RouteItem {
title?: string;
path?: string;
routes?: RouteItem[];
hasSectionHeader?: boolean;
sectionHeader?: string;
}
interface Sidebar {
@@ -22,32 +23,181 @@ interface Sidebar {
routes: RouteItem[];
}
function extractRoutes(
interface Page {
title: string;
url: string;
}
interface SubGroup {
heading: string;
pages: Page[];
}
interface Section {
heading: string | null;
pages: Page[];
subGroups: SubGroup[];
}
// Clean up section header names (remove version placeholders)
function cleanSectionHeader(header: string): string {
return header
.replace(/@\{\{version\}\}/g, '')
.replace(/-/g, ' ')
.split(' ')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
.trim();
}
// Extract routes for sidebars that use hasSectionHeader to define major sections
// (like the API Reference sidebar)
function extractSectionedRoutes(
routes: RouteItem[],
baseUrl: string
): {title: string; url: string}[] {
const result: {title: string; url: string}[] = [];
): Section[] {
const sections: Section[] = [];
let currentSection: Section | null = null;
for (const route of routes) {
if (route.title && route.path) {
result.push({
// Skip external links
if (route.path?.startsWith('http')) {
continue;
}
// Start a new section when we hit a section header
if (route.hasSectionHeader && route.sectionHeader) {
if (currentSection) {
sections.push(currentSection);
}
currentSection = {
heading: cleanSectionHeader(route.sectionHeader),
pages: [],
subGroups: [],
};
continue;
}
// If no section started yet, skip
if (!currentSection) {
continue;
}
// Route with children - create a sub-group
if (route.title && route.routes && route.routes.length > 0) {
const subGroup: SubGroup = {
heading: route.title,
pages: [],
};
// Include parent page if it has a path
if (route.path) {
subGroup.pages.push({
title: route.title,
url: `${baseUrl}${route.path}.md`,
});
}
// Add child pages
for (const child of route.routes) {
if (child.title && child.path && !child.path.startsWith('http')) {
subGroup.pages.push({
title: child.title,
url: `${baseUrl}${child.path}.md`,
});
}
}
if (subGroup.pages.length > 0) {
currentSection.subGroups.push(subGroup);
}
}
// Single page without children
else if (route.title && route.path) {
currentSection.pages.push({
title: route.title,
url: `${baseUrl}${route.path}.md`,
});
}
if (route.routes) {
result.push(...extractRoutes(route.routes, baseUrl));
}
// Don't forget the last section
if (currentSection) {
sections.push(currentSection);
}
return sections;
}
// Extract routes for sidebars that use routes with children as the primary grouping
// (like the Learn sidebar)
function extractGroupedRoutes(
routes: RouteItem[],
baseUrl: string
): SubGroup[] {
const groups: SubGroup[] = [];
for (const route of routes) {
// Skip section headers
if (route.hasSectionHeader) {
continue;
}
// Skip external links
if (route.path?.startsWith('http')) {
continue;
}
// Route with children - create a group
if (route.title && route.routes && route.routes.length > 0) {
const pages: Page[] = [];
// Include parent page if it has a path
if (route.path) {
pages.push({
title: route.title,
url: `${baseUrl}${route.path}.md`,
});
}
// Add child pages
for (const child of route.routes) {
if (child.title && child.path && !child.path.startsWith('http')) {
pages.push({
title: child.title,
url: `${baseUrl}${child.path}.md`,
});
}
}
if (pages.length > 0) {
groups.push({
heading: route.title,
pages,
});
}
}
// Single page without children - group under its own heading
else if (route.title && route.path) {
groups.push({
heading: route.title,
pages: [
{
title: route.title,
url: `${baseUrl}${route.path}.md`,
},
],
});
}
}
return result;
return groups;
}
const sidebars: Sidebar[] = [
sidebarLearn as Sidebar,
sidebarReference as Sidebar,
sidebarBlog as Sidebar,
];
// Check if sidebar uses section headers as primary grouping
function usesSectionHeaders(routes: RouteItem[]): boolean {
return routes.some((r) => r.hasSectionHeader && r.sectionHeader);
}
export const getServerSideProps: GetServerSideProps = async ({res}) => {
const subdomain =
@@ -60,14 +210,48 @@ export const getServerSideProps: GetServerSideProps = async ({res}) => {
'> The library for web and native user interfaces.',
];
const sidebars: Sidebar[] = [
sidebarLearn as Sidebar,
sidebarReference as Sidebar,
];
for (const sidebar of sidebars) {
lines.push('');
lines.push(`## ${sidebar.title}`);
lines.push('');
const routes = extractRoutes(sidebar.routes, baseUrl);
for (const route of routes) {
lines.push(`- [${route.title}](${route.url})`);
if (usesSectionHeaders(sidebar.routes)) {
// API Reference style: section headers define major groups
const sections = extractSectionedRoutes(sidebar.routes, baseUrl);
for (const section of sections) {
if (section.heading) {
lines.push('');
lines.push(`### ${section.heading}`);
}
// Output pages directly under section
for (const page of section.pages) {
lines.push(`- [${page.title}](${page.url})`);
}
// Output sub-groups with #### headings
for (const subGroup of section.subGroups) {
lines.push('');
lines.push(`#### ${subGroup.heading}`);
for (const page of subGroup.pages) {
lines.push(`- [${page.title}](${page.url})`);
}
}
}
} else {
// Learn style: routes with children define groups
const groups = extractGroupedRoutes(sidebar.routes, baseUrl);
for (const group of groups) {
lines.push('');
lines.push(`### ${group.heading}`);
for (const page of group.pages) {
lines.push(`- [${page.title}](${page.url})`);
}
}
}
}