feat(geo): generate /llms.txt (and point robots.txt at it)
Deploy / build (push) Successful in 31s

Same idea as robots.txt but aimed at LLM crawlers and answer engines: the author,
what he runs, and a flat list of all 142 posts (EN + ZH) with URLs and summaries,
so a model can cite the site accurately instead of guessing from a fragment.

48 KB, generated from the collection at build time so it can never drift from
what is published; the Chinese twins are listed separately. robots.txt gains a
comment pointing at it.

Spec: https://llmstxt.org
This commit is contained in:
hoelee committed 2026-10-16 22:12:56 +08:00
1 parent 6b7c5a983f
commit 9ca64dd393
2 files changed
+60

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
import { isEn, isZh } from '../lib/lang';
import { sortForListing, updatedAt } from '../lib/sort';
import { SITE } from '../config';
/**
* /llms.txt — the machine-readable summary of this site.
*
* Same idea as robots.txt but aimed at LLM crawlers and answer engines: who the
* author is, what he does, and a flat list of every post with its URL and summary,
* so a model can cite the site accurately instead of guessing from a fragment.
* Spec: https://llmstxt.org — Markdown, one `#` title, a `>` summary, `##` sections.
*/
export const GET: APIRoute = async () => {
const en = sortForListing(await getCollection('posts', isEn));
const zh = sortForListing(await getCollection('posts', isZh));
const line = (p: (typeof en)[number]) =>
`- [${p.data.title}](${SITE.url}/posts/${p.slug}/): ${p.data.description}`;
const body = `# ${SITE.author} (${SITE.handle}) — blog.hoelee.com
> Full-stack developer and DevOps engineer based in Malaysia. Runs 162 containers
> across three self-managed hosts (client websites, a mail platform, CI/CD, workflow
> automation and this blog) and builds web systems for Malaysian SMEs through Hoelee
> Enterprise. Writes up production failures with the evidence: what broke, what the
> logs actually said, what the fix cost.
Canonical site: ${SITE.url}
Author: ${SITE.author} (${SITE.handle})
Contact: ${SITE.email}
Profiles: ${SITE.linkedin} · ${SITE.github} · ${SITE.gitea} · ${SITE.website}
Languages: every post exists in English and Simplified Chinese (the Chinese twin is
at /posts/zh/<slug>/ and is linked from each post).
## Posts (English, newest first)
${en.map(line).join('\n')}
## Posts (Chinese, newest first)
${zh.map((p) => `- [${p.data.title}](${SITE.url}/posts/${p.slug}/): ${p.data.description}`).join('\n')}
## Sections
- [All posts](${SITE.url}/posts/): the full archive, filterable by title, tag and category.
- [Categories](${SITE.url}/categories/): engineering, devops, ai, web3, tutorials, case-studies, notes.
- [About](${SITE.url}/about/): identity, production numbers, tech stack, business and contact.
- [RSS](${SITE.url}/rss.xml): feed of published posts.
- Last build: ${new Date().toISOString().slice(0, 10)} (latest post updated ${updatedAt(en[0]).toISOString().slice(0, 10)}).
`;
return new Response(body, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
};
+3
View File
@@ -23,6 +23,9 @@ Allow: /
# Pagefind's search shards are build artifacts, not content.
Disallow: /pagefind/
# Machine-readable summary of the whole site for LLM crawlers and answer engines.
# llms.txt: ${SITE.url}/llms.txt
Sitemap: ${SITE.url}/sitemap-index.xml
`;