Skip to content

Commit 933e30a

Browse files
ThallesPclaude
andcommitted
Add llms-txt standard support at /llms.txt and /llms-full.txt
- /llms.txt — new lightweight index with links and descriptions per the llms-txt standard (https://llmstxt.org/) - /llms-full.txt — rewrites to the existing /api/llms-docs.md endpoint which already inlines all page content Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7f056f9 commit 933e30a

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

next.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ const nextConfig = {
2020
async redirects() {
2121
return redirects;
2222
},
23+
async rewrites() {
24+
return [
25+
{
26+
source: "/llms.txt",
27+
destination: "/api/llms.txt",
28+
},
29+
{
30+
source: "/llms-full.txt",
31+
destination: "/api/llms-docs.md",
32+
},
33+
];
34+
},
2335
};
2436

2537
module.exports = withContentCollections(nextConfig);

src/pages/api/llms.txt.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { NextApiHandler } from "next";
2+
import { sidebarContent } from "@/data/sidebar";
3+
import { IPage, ISidebarSection, ISubSection } from "@/types";
4+
import fs from "fs";
5+
import path from "path";
6+
import matter from "gray-matter";
7+
8+
const BASE_URL =
9+
process.env.NEXT_PUBLIC_RAILWAY_DOCS_URL || "https://docs.railway.com";
10+
11+
/**
12+
* Reads the frontmatter description for a given page slug.
13+
*/
14+
function getPageDescription(slug: string): string | undefined {
15+
const possiblePaths = [
16+
path.join(process.cwd(), "content/docs", slug.replace(/^\//, "") + ".md"),
17+
path.join(
18+
process.cwd(),
19+
"content/guides",
20+
slug.replace(/^\/guides\//, "") + ".md",
21+
),
22+
];
23+
24+
for (const filePath of possiblePaths) {
25+
try {
26+
const fileContent = fs.readFileSync(filePath, "utf8");
27+
const { data: frontMatter } = matter(fileContent);
28+
return frontMatter.description;
29+
} catch {
30+
continue;
31+
}
32+
}
33+
34+
return undefined;
35+
}
36+
37+
/**
38+
* Extracts all pages from a sidebar section, handling subsections.
39+
*/
40+
function getSectionPages(section: ISidebarSection): IPage[] {
41+
const pages: IPage[] = [];
42+
for (const item of section.content) {
43+
if ("url" in item) {
44+
continue; // Skip external links
45+
} else if ("subTitle" in item) {
46+
const sub = item as ISubSection;
47+
if (typeof sub.subTitle !== "string") {
48+
pages.push(sub.subTitle);
49+
}
50+
for (const page of sub.pages) {
51+
if (!("url" in page)) {
52+
pages.push(page);
53+
}
54+
}
55+
} else {
56+
pages.push(item);
57+
}
58+
}
59+
return pages;
60+
}
61+
62+
const handler: NextApiHandler = async (_req, res) => {
63+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
64+
res.setHeader(
65+
"Cache-Control",
66+
"public, s-maxage=3600, stale-while-revalidate=86400",
67+
);
68+
69+
let content = `# Railway Documentation\n\n`;
70+
content += `> Railway is a deployment platform that lets you provision infrastructure, develop locally with that infrastructure, and deploy to the cloud.\n\n`;
71+
content += `- [Full documentation](${BASE_URL}/llms-full.txt): The complete Railway documentation with all page content inlined\n\n`;
72+
73+
for (const section of sidebarContent) {
74+
const sectionTitle = section.title || "Getting Started";
75+
const pages = getSectionPages(section);
76+
77+
if (pages.length === 0) continue;
78+
79+
content += `## ${sectionTitle}\n\n`;
80+
81+
for (const page of pages) {
82+
const url = `${BASE_URL}${page.slug}`;
83+
const description = getPageDescription(page.slug);
84+
if (description) {
85+
content += `- [${page.title}](${url}): ${description}\n`;
86+
} else {
87+
content += `- [${page.title}](${url})\n`;
88+
}
89+
}
90+
91+
content += "\n";
92+
}
93+
94+
res.status(200).send(content.trimEnd() + "\n");
95+
};
96+
97+
export default handler;

0 commit comments

Comments
 (0)