I added llms.txt to this site built with Astro, so I’ll introduce the code.
First, I searched for Astro Integrations (plugins) that could easily add llms.txt. At present, none that support llms.txt were found.
Since there was no plugin and it would be faster to create it myself this time, I asked Cursor (claude-3.7-sonnet) Agent mode to implement it.
The overview of specifications I described in the prompt was as follows:
Please implement to return responses in the following format for all pages
- [title](url): description
Here’s the code created through Vibe Coding with Cursor.
(Please modify appropriately according to your project structure and specific requirements.)
src/pages/llms.txt.js
/**
* Generate a complete site map with formatted Markdown links
*/
export async function GET(context) {
const baseUrl = "https://codenote.net";
// Get data
const posts = await fetchAllPosts();
const tags = extractAllTags(posts);
// Generate formatted content
const formattedEntries = [
...generateHeader(),
...generateStaticPages(baseUrl),
...generatePostsSection(posts, baseUrl),
...generateTagsSection(tags, baseUrl)
];
// Return response as a text file
return new Response(formattedEntries.join('\n'), {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
}
/**
* Fetch all posts from the posts directory
*/
async function fetchAllPosts() {
const postFiles = await import.meta.glob('./posts/*.md', { eager: true });
return Object.values(postFiles);
}
/**
* Extract all unique tags from posts
*/
function extractAllTags(posts) {
const tagSet = new Set();
posts.forEach(post => {
if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) {
post.frontmatter.tags.forEach(tag => tagSet.add(tag));
}
});
return Array.from(tagSet);
}
/**
* Generate the document header
*/
function generateHeader() {
return [
`# CodeNote.net`,
``,
];
}
/**
* Generate links for static pages
*/
function generateStaticPages(baseUrl) {
return [
`- [Home](${baseUrl}/): Top page of CodeNote`,
``,
`## Second directories`,
``,
`- [About](${baseUrl}/about): About page and profile information`,
`- [Posts](${baseUrl}/posts): All blog posts`,
`- [Tags](${baseUrl}/tags): All available tags`,
`- [RSS Feed](${baseUrl}/rss.xml): Subscribe to the blog updates`,
];
}
/**
* Generate the posts section with links to all posts
*/
function generatePostsSection(posts, baseUrl) {
const result = [
``,
`## Posts`,
``
];
posts.forEach(post => {
const url = getPostUrl(post, baseUrl);
if (url) {
const title = post.frontmatter?.title || "Untitled";
const description = post.frontmatter?.description || "";
result.push(`- [${title}](${url}): ${description}`);
}
});
return result;
}
/**
* Get the URL for a post
*/
function getPostUrl(post, baseUrl) {
if (post.url) {
return `${baseUrl}${post.url}`;
} else if (post.file) {
const filename = post.file.split('/').pop()?.replace(/\.md$/, '');
if (filename) {
return `${baseUrl}/posts/${filename}`;
}
}
return null;
}
/**
* Generate the tags section with links to all tag pages
*/
function generateTagsSection(tags, baseUrl) {
const result = [
``,
`## Tags`,
``
];
tags.forEach(tag => {
result.push(`- [#${tag}](${baseUrl}/tags/${tag}): Posts tagged with #${tag}`);
});
return result;
}
export const prerender = true;
That’s all from the Gemba.