Make your Next.js site legible to AI agents — generate a spec-compliant
llms.txt manifest and serve any page as raw markdown.
// app/llms.txt/route.ts
import { generateLlmsText } from "@vllnt/next-llms";
export function GET() {
return new Response(
generateLlmsText({
title: "Acme",
summary: "Acme is a widget API.",
sections: [
{
title: "Docs",
links: [{ title: "Quickstart", url: "/docs/quickstart.md" }],
},
],
}),
{ headers: { "Content-Type": "text/plain; charset=utf-8" } },
);
}Emit the llms.txt index and expose any page's markdown at /path.md so agents
skip HTML parsing. Zero runtime dependencies — pure Web-standard APIs that run
in any Next.js runtime (and Remix, Hono, Deno, …).
- Spec-compliant
llms.txt—generateLlmsTextemits the llmstxt.org shape: an#title, a>blockquote,## Sectionlink lists, and a droppable## Optionalsection. - Raw-markdown routes —
createMarkdownRouteserves any page astext/markdown, stripping a trailing.mdand resolving the slug through your own content source. - Zero dependencies — pure TypeScript over Web
Request/Response; nothing added to your bundle. - Runtime-agnostic — the route handler imports nothing from
next; it runs on the Edge, Node, or any Web-standard runtime. - Typed end to end — concrete config types guide correct usage; no
any. - Cache-friendly — sensible
Cache-Controldefaults, overridable per route.
pnpm add @vllnt/next-llms@canaryNode.js >=18; no peer dependencies. This README follows current main and the
canary channel (0.1.0-canary.3cfe2be at this audit). npm latest also
points to a prerelease, but an older one (0.1.0-canary.bca5ab7), so an
untagged install does not select current main. There is no stable release on
latest yet. Pin the resolved prerelease and test upgrades. Current main
removed the older generateLlmsFullText API; use the two exports below.
// app/[...slug]/route.ts — serve any page as raw markdown
import { createMarkdownRoute } from "@vllnt/next-llms";
export const GET = createMarkdownRoute(async (slug) => {
const doc = await getDoc(slug.join("/")); // your content source
return doc?.markdown ?? null; // null → 404
});/docs/intro.md resolves the slug ["docs", "intro"] and returns the markdown
with Content-Type: text/markdown, or 404 when the resolver returns null.
| Export | Kind | Result |
|---|---|---|
generateLlmsText(config) |
function | string — the llms.txt index |
createMarkdownRoute(resolver, options?) |
factory | (request: Request) => Promise<Response> |
Full reference: docs/API.md. Also see the LLM index, changelog, and issue tracker.
- No secrets touched — the package generates text and serves whatever your resolver returns.
- Your resolver is the boundary — validate and authorize the slug before returning content.
- Successful responses default to public caching (one hour in browsers, one
day in shared caches). For private or personalized content, explicitly pass
{ cacheControl: "private, no-store" }; authorization alone does not disable caching.
See docs/API.md and the security reporting policy.
pnpm test # single run
pnpm test:coverage # enforced 100% on covered filesSee CONTRIBUTING.md.
Built by bntvllnt · bntvllnt.com · X @bntvllnt
Part of the @vllnt open-source fleet — vllnt.com
If this is useful, sponsor the work.
MIT — see LICENSE.