npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nuxt-llms-txt

v0.0.2

Published

Make your Nuxt site readable by AI. Markdown mirrors of every page, plus a properly sectioned llms.txt — zero config, source-aware.

Readme

nuxt-llms-txt

npm version npm downloads License

Make your Nuxt site readable by AI. A Markdown mirror of every page, plus a properly sectioned llms.txt index — zero config, and source-aware.

npx nuxi module add nuxt-llms-txt
export default defineNuxtConfig({
  modules: ['nuxt-llms-txt'],
})

That's it. Build the site and you get:

| Route | What it is | | --- | --- | | /about.md | Clean Markdown mirror of /about, with title/description frontmatter | | /about + Accept: text/markdown | The same Markdown, via content negotiation (Vary: Accept) | | /llms.txt | A sectioned index of the whole site, per llmstxt.org | | /llms-full.txt | Every page inlined, for one-shot ingestion (opt in with full: true) |

Every HTML page also advertises its mirror with <link rel="alternate" type="text/markdown"> and a matching Link: response header.

Why another one of these

There are two existing options, and neither does the whole job:

| | nuxt-llms-txt | nuxt-llms | @mdream/nuxt | | --- | :---: | :---: | :---: | | llms.txt | ✅ | ✅ | ✅ | | Per-page .md mirrors | ✅ | ❌ | ✅ | | Routes discovered automatically | ✅ | ❌ hand-written config | ✅ | | Publishes your original Markdown when a page has a source file | ✅ | n/a | ❌ HTML round-trip | | Sectioned index with real descriptions | ✅ | manual | ❌ flat list | | Per-locale llms.txt | ✅ | ❌ | ❌ | | Works on a plain Vue-pages app (no @nuxt/content) | ✅ | ❌ | ✅ |

The differences that matter in practice:

It publishes your source, not a round-trip. If a page came from a Markdown file, that file's Markdown is what gets served — MDC components, fence labels, tables and all. Converting rendered HTML back to Markdown loses every one of those. Pages that aren't Markdown-backed fall back to HTML conversion automatically, so a mixed site works either way.

::alert{type="warning"}
Keep your key on the server.
::

```bash [npm]
npm install @acme/sdk
```
Keep your key on the server.

```
npm install @acme/sdk
```
::alert{type="warning"}
Keep your key on the server.
::

```bash [npm]
npm install @acme/sdk
```

The index is organised. Sections are derived from your route tree, and a top-level segment only becomes its own heading once it has children — so a flat marketing site gets one clean list, and a docs site gets ## Docs / ## Blog:

# Acme Inc

> Acme builds tools for building tools.

## Pages

- [Acme Inc](https://acme.test/index.md): Acme builds developer tools that get out of your way.
- [Pricing](https://acme.test/pricing.md): Simple, transparent pricing for teams of every size.

## Docs

- [Documentation](https://acme.test/docs.md): Guides, API reference and recipes.
- [Getting started](https://acme.test/docs/getting-started.md): Install the SDK and make your first request.

## Optional

- [Terms of service](https://acme.test/legal/terms.md): The legal agreement between you and Acme Inc.

Descriptions are real. They come from useSeoMeta / og:description / frontmatter, falling back to the first substantial paragraph — and optionally from an LLM (see below).

How it works

.md files and llms.txt are written during prerendering, so a static build (nuxi generate) needs no server at all. For SSR routes that are never prerendered, a Nitro middleware renders and converts on demand — so /team/ada-lovelace.md works even though that page only exists at request time.

Content is isolated before conversion: <nav>, <header>, <footer>, <aside>, <script> and anything marked data-ai-ignore are stripped, so the Markdown is the page, not the chrome.

<template>
  <main>
    <h1>Pricing</h1>

    <div data-ai-ignore>
      <CookieBanner />
    </div>
  </main>
</template>

Content negotiation

Markdown is served from the HTML route only when the client explicitly asks for it and does not ask for HTML:

| Accept | Response | | --- | --- | | text/html,... (any browser) | HTML | | text/markdown | Markdown | | */* (curl, most HTTP libraries) | HTML |

The */* case deliberately keeps returning HTML — guessing otherwise would break ordinary API consumers. Disable the whole behaviour with negotiate: false.

Configuration

Everything is optional. Defaults shown.

export default defineNuxtConfig({
  modules: ['nuxt-llms-txt'],

  llmsTxt: {
    // Absolute site URL, used for absolute links in llms.txt.
    // Inferred from `site.url` or NUXT_PUBLIC_SITE_URL when omitted.
    domain: 'https://example.com',

    // llms.txt header. `title` falls back to `site.name`.
    title: 'Example',
    description: 'What this site is.',
    notes: 'Free-form prose under the summary.',

    // Which routes to map. `exclude` is *merged* with the built-in defaults
    // (/api/**, /_**, /200, /404), so you never have to re-list them.
    include: ['/**'],
    exclude: [],

    // Routes listed under the conventional `## Optional` heading.
    optional: ['/legal/**'],

    // Override the derived sections entirely.
    sections: [
      { title: 'Documentation', match: '/docs/**', description: 'Guides and API reference.' },
      { title: 'Blog', match: '/blog/**' },
    ],

    // Content isolation.
    mainSelector: ['main', '[role="main"]', 'article', '#__nuxt', 'body'],
    stripSelector: ['nav', 'header', 'footer', 'aside', '[aria-hidden="true"]', '[data-ai-ignore]'],

    // Outputs.
    markdownFiles: true,   // write /foo.md next to every prerendered page
    full: false,           // also emit /llms-full.txt — or { maxChars: 400_000 }

    // Runtime behaviour.
    runtime: true,         // serve .md for SSR routes
    negotiate: true,       // honour `Accept: text/markdown`
    linkAlternate: true,   // <link rel="alternate"> + Link: header

    // Prefer original Markdown sources. Auto-detected with @nuxt/content;
    // point it somewhere else with { dir: 'docs' }, or turn it off.
    source: true,
  },
})

AI-written descriptions (opt in)

Pages with no description can have one written for them at build time.

llmsTxt: {
  ai: {
    provider: 'anthropic',              // or 'openai'
    model: 'claude-opus-5',             // default; 'gpt-4o-mini' for openai
    apiKey: process.env.ANTHROPIC_API_KEY,
    only: 'missing',                    // or 'all' to rewrite every description
    concurrency: 5,
  },
}

Three things make this safe to leave in a shared repo:

  • No key, no calls. Without apiKey the step is skipped entirely, so contributors and CI build the site without an account.
  • Results are cached in node_modules/.cache/nuxt-llms-txt/ keyed by a content hash, so a rebuild costs nothing and only edited pages are re-billed.
  • Failures degrade. A rate limit or refusal leaves the existing description in place and logs a warning; it never fails your build.

Install whichever SDK you use — they are optional peer dependencies:

pnpm add -D @anthropic-ai/sdk   # or: openai

Or bypass both providers entirely:

llmsTxt: {
  ai: {
    summarize: async page => myOwnSummarizer(page.markdown),
  },
}

i18n

With @nuxtjs/i18n installed, locales are detected automatically. Each gets its own index at /{locale}/llms.txt listing only that locale's pages, and the primary index cross-links the others under ## Optional. Locale prefixes are stripped before sections are derived, so /fr/docs/intro lands under ## Docs alongside its English twin — not in a section called "Fr".

Hooks

Both run at build time, after prerendering.

export default defineNuxtConfig({
  hooks: {
    // Mutate the page corpus: drop pages, rewrite Markdown, fix titles.
    'llms-txt:pages'(pages) {
      for (const page of pages) {
        if (page.origin === 'html') page.markdown = tweak(page.markdown)
      }
    },

    // Mutate, add or drop output files just before they are written.
    'llms-txt:files'(files) {
      files.push({ path: '/llms-extra.txt', contents: '...', kind: 'llms.txt' })
    },
  },
})

Using the core without Nuxt

The conversion and llms.txt machinery is framework-free and published separately, for scripts, CI checks or another framework's adapter:

import { buildLlmsTxt, htmlToMarkdown } from 'nuxt-llms-txt/core'

const { title, description, markdown } = htmlToMarkdown(html, {
  domain: 'https://example.com',
})

const index = buildLlmsTxt(pages, { title: 'Example', domain: 'https://example.com' })

Notes and limits

  • llms.txt needs prerendering. The index is built from prerendered pages, so a pure-SSR build with no prerendered routes produces none. Set nitro.prerender.crawlLinks: true (or list routes) to get one. Per-page .md mirrors do not need this — the runtime middleware covers them.
  • Content source mapping follows @nuxt/content's default path rules (numeric 1. prefixes stripped, index folded into its parent). Custom source prefixes may not map; override individual routes with sourceMap, or set source: false to always convert from HTML.
  • Pages marked noindex are excluded from both the index and the mirrors.

Contributing

pnpm install
pnpm dev:prepare      # generate module + playground types
pnpm dev              # playground at localhost:3000
pnpm verify           # lint + types + tests + build

Two playgrounds cover both paths: playground/ is a plain Vue-pages app, playground-content/ is a @nuxt/content site.

License

MIT