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

@happyalienai/vite-plugin-llm-spider

v0.3.0

Published

Vite plugin that generates LLM-friendly Markdown snapshots and llms.txt for SPAs

Readme

vite-plugin-llm-spider

Make your Vite SPA discoverable by AI search engines like ChatGPT, Perplexity, and Google AI Overviews

Single Page Apps are invisible to AI. While tools like ChatGPT, Claude, and Perplexity reshape how people find information, SPAs remain hidden behind JavaScript walls. This plugin bridges that gap by generating clean, LLM-friendly markdown snapshots and a standardized index.

  • 🤖 Zero-click optimization — Get cited in AI answers without complex SSR
  • 📈 LLM SEO / GEO ready — Clean markdown format that LLMs prefer
  • 🎯 llms.txt standard — Machine-readable index for AI agents

Built by Happy Alien AI — We take the busy work out of training development.

Why This Matters

Traditional SEO optimizes for Google's crawler. Generative Engine Optimization (GEO) optimizes for AI systems that synthesize answers from your content. When someone asks ChatGPT or Perplexity a question your site answers, you want to be cited.

SPAs render content via JavaScript — invisible to most AI crawlers. This plugin runs Puppeteer at build time to capture your rendered pages as clean markdown, plus generates an llms.txt index that tells AI agents exactly where to look.

Features

  • 🕷️ Two discovery modes: explicit route list (recommended) or controlled BFS crawl
  • 📝 Markdown output: clean, readable .md files following the llms.txt spec
  • 🧹 Noise removal: strips nav, footer, modals, cookie banners, etc.
  • 🔒 Safe by default: explicit excludes, no auth pages by accident
  • Works with any Vite framework: Vue, React, Svelte, Solid, etc.

Installation

npm i -D @happyalienai/vite-plugin-llm-spider

Quick Start

The plugin auto-discovers your pages by crawling from the root — no configuration needed:

// vite.config.js
import { defineConfig } from "vite";
import llmSpider from "@happyalienai/vite-plugin-llm-spider";

export default defineConfig({
  plugins: [
    llmSpider(),  // That's it! Crawl is enabled by default
  ],
});

After npm run build, you'll get:

  • dist/llms.txt — auto-generated index of all discovered pages
  • dist/*.md — markdown snapshot for each page

Customize defaults

llmSpider({
  exclude: ["/login", "/admin", "/account"],  // Skip sensitive pages
  crawl: {
    maxDepth: 3,    // How deep to follow links (default: 2)
    maxPages: 100,  // Max pages to capture (default: 50)
  },
  render: {
    waitForSelector: "main",  // Wait for content before extracting
  },
})

Output Format

The generated llms.txt follows the llmstxt.org spec:

# My Site

> LLM-friendly index of important pages and their Markdown equivalents.

## Product

- [Home](index.html.md)
- [Pricing](pricing.md)

## Optional

- [Docs](docs/index.html.md)

Configuration

Static Mode (No Browser Required)

For pre-rendered/SSG sites, you can skip Puppeteer entirely and read HTML files directly:

llmSpider({
  static: true,  // Read from dist/ without browser
  routes: [      // Required when using static mode
    { path: "/", title: "Home" },
    { path: "/about", title: "About" },
  ],
  crawl: { enabled: false },
})

Options:

  • true — Always read HTML files directly (no browser)
  • false — Always use Puppeteer (required for SPAs that need JS rendering)
  • "auto" (default) — Use static when crawl is disabled, browser when crawl is enabled

Static mode is ideal for CI environments without Chrome (WSL, Docker, etc.).

Explicit Routes (Advanced)

For precise control over which pages to include (disables crawl):

llmSpider({
  routes: [
    {
      path: "/pricing",      // URL path (required)
      title: "Pricing",      // Display title in llms.txt
      section: "Product",    // H2 section grouping
      optional: false,       // If true, goes under "## Optional"
      notes: "Updated weekly" // Appended to link in llms.txt
    }
  ],
  crawl: { enabled: false },  // Disable crawl when using explicit routes
})

Use explicit routes when you want curated control over your llms.txt index rather than auto-discovery.

Crawl Options

Crawl is enabled by default. Customize the behavior:

llmSpider({
  crawl: {
    enabled: true,       // Disable with `false` if using explicit routes
    seeds: ["/"],        // Starting URLs
    maxDepth: 2,         // Link depth from seeds
    maxPages: 50,        // Cap on total pages
    concurrency: 3,      // Parallel page loads
    stripQuery: true,    // Ignore ?query params
  },
  exclude: ["/login", "/admin"],
})

Rendering Options

render: {
  waitUntil: "networkidle2",   // Puppeteer wait strategy
  timeoutMs: 30_000,           // Page load timeout
  waitForSelector: "main",     // Wait for element before extracting
  postLoadDelayMs: 200,        // Extra delay after load
  blockRequests: [             // Block analytics/trackers
    /google-analytics\.com/i,
    /hotjar\.com/i,
  ],
  launchOptions: {             // Puppeteer launch options
    headless: "new",
    args: ["--no-sandbox"],    // For CI/Docker
  },
}

Extraction Options

extract: {
  mainSelector: ["main", "#content", "[data-main]"],  // Content selectors (first match wins)
  removeSelectors: [                                   // Elements to strip
    "nav", "header", "footer", "svg", ".modal", ".cookie-banner"
  ],
}

Output Options

output: {
  mode: "sibling",              // "sibling" (default) or "subdir"
  subdir: "ai",                 // Subdir name when mode="subdir"
  llmsTxtFileName: "llms.txt",  // Index filename
  llmsTitle: "My App",          // H1 title
  llmsSummary: "AI-friendly pages",  // Summary blockquote
  sort: true,                   // Alphabetical ordering
}

Markdown Options

markdown: {
  addFrontmatter: true,  // Add YAML frontmatter with source/title/date
  turndown: {            // Turndown options
    headingStyle: "atx",
    codeBlockStyle: "fenced",
  },
}

URL Mapping

Following the llms.txt spec:

| Route | Output File | |-------|-------------| | / | index.html.md | | /pricing | pricing.md | | /docs/ | docs/index.html.md | | /docs/api | docs/api.md |

Hooks

render: {
  async beforeGoto(page, { route }) {
    // Inject auth token for protected pages (use carefully!)
    await page.evaluate(() => {
      localStorage.setItem("token", "dev-token");
    });
  },
  async beforeExtract(page, { route }) {
    // Custom cleanup before extraction
  },
}

CI/Docker

For headless environments:

render: {
  launchOptions: {
    headless: "new",
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  },
}

Troubleshooting

Timeouts

  • Use waitForSelector: "main" instead of relying on networkidle
  • Increase timeoutMs or add postLoadDelayMs

Output is mostly nav/footer

  • Tighten mainSelector to your content wrapper
  • Add more removeSelectors

CI fails to launch browser

  • Easiest fix: Use static: true if you don't need JS rendering
  • Add --no-sandbox to launch args
  • Ensure Puppeteer dependencies are installed

License

MIT