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:
1 parent
6b7c5a983f
commit
9ca64dd393
2 files changed
+60
No files matched your search
@@ -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' },
|
||||
});
|
||||
};
|
||||
@@ -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
|
||||
`;
|
||||
|
||||
|
||||
Reference in new issue
Block a user