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

@vercel/agent-readability

v0.4.0

Published

Detect AI agents. Serve them markdown. Audit your site against the Agent Readability Spec.

Readme

@vercel/agent-readability

Detect AI agents. Serve them markdown. Audit your site against the Agent Readability Spec.

Install

npm install @vercel/agent-readability

Or audit without installing:

npx @vercel/agent-readability audit https://vercel.com/docs

Quick Start

Next.js middleware.ts:

import { withAgentReadability } from '@vercel/agent-readability/next'

export default withAgentReadability({
  rewrite: (pathname) => `/api/docs-md${pathname}`,
})

export const config = { matcher: ['/docs/:path*'] }

SvelteKit hooks.server.ts:

import { handleAgentReadability } from '@vercel/agent-readability/sveltekit'
import { sequence } from '@sveltejs/kit/hooks'

export const handle = sequence(
  handleAgentReadability({ rewrite: (p) => `/api/docs-md${p}` }),
)

Nuxt server/middleware/agent.ts:

import { defineAgentMiddleware } from '@vercel/agent-readability/nuxt'

export default defineAgentMiddleware({
  getMarkdown: async (pathname) => {
    const doc = await fetchDoc(pathname)
    return doc.markdown
  },
})

Agents hitting /docs/* get markdown instead of HTML.

How Detection Works

Three layers, checked in order:

  1. Known UA patterns. 30+ agents (ClaudeBot, GPTBot, Cursor, Perplexity, etc.)
  2. Signature-Agent header. ChatGPT agent via RFC 9421.
  3. sec-fetch-mode heuristic. Unknown bots lacking browser fingerprints.

Optimizes for recall over precision. Serving markdown to a non-AI bot is cheap. Missing an AI agent is not.

Core API

isAIAgent(request)

import { isAIAgent } from '@vercel/agent-readability'

const result = isAIAgent(request)
// { detected: true, method: 'ua-match' }

Accepts any object with headers.get() (Request, NextRequest, etc.)

Returns:

  • { detected: true, method: 'ua-match' | 'signature-agent' | 'heuristic' }
  • { detected: false, method: null }

acceptsMarkdown(request)

import { acceptsMarkdown } from '@vercel/agent-readability'

if (acceptsMarkdown(request)) {
  return new Response(markdown, {
    headers: { 'Content-Type': 'text/markdown', 'Vary': 'Accept' },
  })
}

shouldServeMarkdown(request)

Combines detection + content negotiation.

import { shouldServeMarkdown } from '@vercel/agent-readability'

const { serve, reason } = shouldServeMarkdown(request)
// serve: true, reason: 'agent' | 'accept-header'

generateNotFoundMarkdown(path, options?)

Markdown body for missing pages. Return with 200 on the canonical URL (agents discard 404 bodies).

import { generateNotFoundMarkdown } from '@vercel/agent-readability'

const md = generateNotFoundMarkdown('/docs/missing', {
  baseUrl: 'https://example.com',
})
return new Response(md, {
  headers: { 'Content-Type': 'text/markdown', 'Vary': 'Accept' },
})

Keep suggested page links canonical (/docs/page) and negotiate markdown with Accept: text/markdown rather than exposing .md page URLs in not-found responses.

Pattern Exports

AI_AGENT_UA_PATTERNS, TRADITIONAL_BOT_PATTERNS, SIGNATURE_AGENT_DOMAINS, and BOT_LIKE_REGEX are all exported.

Next.js Adapter

withAgentReadability(options, handler?)

Works with Next.js 14 and 15 (Pages and App Router).

import { withAgentReadability } from '@vercel/agent-readability/next'

export default withAgentReadability({
  docsPrefix: '/docs',
  rewrite: (pathname) => `/en/llms.mdx/${pathname.replace('/docs/', '')}`,
  onDetection: async ({ path, method }) => {
    await trackMdRequest({ path, detectionMethod: method })
  },
})

onDetection runs via event.waitUntil() and does not block the response.

Composing with existing middleware

export default withAgentReadability(
  { rewrite: (p) => `/md${p}` },
  (req, event) => i18nMiddleware(req, event),
)

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | docsPrefix | string | '/docs' | URL prefix to intercept | | rewrite | (pathname: string) => string | required | Maps request path to markdown route | | onDetection | (info) => void \| Promise<void> | - | Analytics callback (runs in waitUntil) |

agentReadabilityMatcher

Excludes Next.js internals and static files. Use for site-wide detection:

import { withAgentReadability, agentReadabilityMatcher } from '@vercel/agent-readability/next'

export default withAgentReadability({
  docsPrefix: '/',
  rewrite: (pathname) => `/md${pathname}`,
})

export const config = {
  matcher: agentReadabilityMatcher,
}

SvelteKit Adapter

handleAgentReadability(options)

Returns a Handle function. Requires SvelteKit 2+. Uses event.fetch() for zero-cost internal routing to your +server.ts markdown routes.

// hooks.server.ts
import { handleAgentReadability } from '@vercel/agent-readability/sveltekit'
import { sequence } from '@sveltejs/kit/hooks'

export const handle = sequence(
  handleAgentReadability({
    docsPrefix: '/docs',
    rewrite: (pathname) => `/api/docs-md${pathname}`,
    onDetection: ({ path, method }) => {
      console.log(`Agent detected: ${method} on ${path}`)
    },
  }),
)

Automatically guards against infinite loops (isSubRequest), skips client navigation requests (isDataRequest), and falls through if the rewrite target returns non-OK.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | docsPrefix | string | '/docs' | URL prefix to intercept | | rewrite | (pathname: string) => string | required | Maps request path to +server.ts route | | onDetection | (info) => void \| Promise<void> | - | Fire-and-forget analytics callback |

Nuxt Adapter

defineAgentMiddleware(options)

Wraps defineEventHandler. Requires h3 1.8+ (ships with Nuxt 3/4). Uses a getMarkdown callback instead of rewrite since Nuxt has no zero-cost internal fetch.

// server/middleware/agent.ts
import { defineAgentMiddleware } from '@vercel/agent-readability/nuxt'

export default defineAgentMiddleware({
  docsPrefix: '/docs',
  getMarkdown: async (pathname, event) => {
    const doc = await queryContent(pathname).findOne()
    return doc.body
  },
})

getMarkdown can return a string (auto-wrapped with text/markdown headers) or a Response for full control.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | docsPrefix | string | '/docs' | URL prefix to intercept | | getMarkdown | (pathname, event) => string \| Response \| Promise<...> | required | Returns markdown content | | onDetection | (info) => void \| Promise<void> | - | Fire-and-forget analytics callback |

Audit CLI

npx @vercel/agent-readability audit https://sdk.vercel.ai

25 weighted checks across 4 categories. Score 0-100. Failed checks include fix suggestions you can paste into your coding agent.

CI

- name: Audit agent readability
  run: npx @vercel/agent-readability audit ${{ env.SITE_URL }} --min-score 70 --json

Exit code 1 if score is below threshold.

| Flag | Description | |------|-------------| | --json | Output as JSON | | --min-score <n> | Exit with error if score < n |

Caching

Set Vary: Accept on markdown responses so CDNs don't serve cached HTML to agents (or cached markdown to browsers).

The SvelteKit and Nuxt adapters set Vary: Accept automatically. The Next.js adapter rewrites the URL, so your markdown route handler must set Vary: Accept and Content-Type: text/markdown.

Edge Runtime

Core library and all adapters use Web APIs only. Works in Vercel Edge Runtime, Cloudflare Workers, and any Request/Response environment.

CLI requires Node.js 20+.

License

MIT