Added llms.txt to Astro Website

Tadashi Shigeoka ·  Tue, April 29, 2025

I added llms.txt to this site built with Astro, so I’ll introduce the code.

Exploring Astro Plugins

First, I searched for Astro Integrations (plugins) that could easily add llms.txt. At present, none that support llms.txt were found.

AI-Assisted Implementation

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

llms.txt Implementation Code

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.