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

@seoengine.ai/next-llm-ready

v1.0.4

Published

Next.js SEO for AI - Make your content LLM-ready with copy-to-AI buttons, markdown export, Table of Contents, /llms.txt endpoint. Works alongside next-seo for complete SEO coverage.

Readme

@seoengine.ai/next-llm-ready

Next.js SEO for the AI Era | LLM-Ready Content Components



Why next-llm-ready? The Future of Next.js SEO

Traditional SEO is no longer enough. With AI assistants like ChatGPT, Claude, Gemini, and Perplexity becoming primary ways users discover content, your Next.js SEO strategy needs to evolve. While packages like next-seo handle meta tags for Google, next-llm-ready makes your content discoverable and consumable by AI.

Large Language Models (LLMs) are increasingly used to consume web content. next-llm-ready makes your Next.js content instantly accessible to AI assistants by:

  • One-click copy - Users can copy your content as clean Markdown for ChatGPT, Claude, etc.
  • LLM-optimized endpoints - /llms.txt and ?llm=1 for AI crawlers and LLM agents
  • Table of Contents - Auto-generated navigation with active heading tracking
  • Analytics - Track how users interact with your AI-ready content
  • Zero config - Works out of the box with sensible defaults
  • TypeScript-first - Full type safety like you expect from modern Next.js packages

next-seo vs next-llm-ready: Complete Next.js SEO Strategy

| Feature | next-seo | next-llm-ready | |---------|----------|----------------| | Meta tags (title, description) | ✅ | - | | Open Graph / Twitter Cards | ✅ | - | | JSON-LD structured data | ✅ | - | | Sitemap generation | ✅ | - | | LLM-friendly /llms.txt | - | ✅ | | Markdown export (?llm=1) | - | ✅ | | Copy-to-AI buttons | - | ✅ | | Table of Contents | - | ✅ | | AI crawler support | - | ✅ |

Use both packages together for complete Next.js SEO coverage:

  • next-seo → Google, Bing, social media
  • next-llm-ready → ChatGPT, Claude, Gemini, Perplexity, AI agents

Quick Start

Installation

npm install @seoengine.ai/next-llm-ready
# or
yarn add @seoengine.ai/next-llm-ready
# or
pnpm add @seoengine.ai/next-llm-ready

Import Styles

// app/layout.tsx or _app.tsx
import '@seoengine.ai/next-llm-ready/styles.css';

Add Copy Button

import { CopyButton } from '@seoengine.ai/next-llm-ready';

export default function Article({ title, content, url }) {
  return (
    <article>
      <CopyButton
        content={{ title, content, url }}
        text="Copy for AI"
      />
      <h1>{title}</h1>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </article>
  );
}

That's it! Your content is now AI-ready.


Features

| Feature | Description | |---------|-------------| | CopyButton | Simple copy button with success feedback | | CopyDropdown | Split button with copy/view/download menu | | TOC | Sticky table of contents with active tracking | | LLMBadge | Visual indicator that content is AI-ready | | useLLMCopy | Hook for custom copy implementations | | useTOC | Hook for custom TOC implementations | | /llms.txt | Sitemap-like file for AI crawlers | | ?llm=1 | Markdown version of any page | | Analytics | Track copy events and engagement |


Components

CopyButton

Simple button that copies content as Markdown to clipboard.

import { CopyButton } from '@seoengine.ai/next-llm-ready';

<CopyButton
  content={{
    title: "Getting Started with Next.js",
    content: "<p>Next.js is a React framework...</p>",
    url: "https://example.com/nextjs-guide",
    author: "John Doe",
    publishedAt: "2024-01-15",
    tags: ["nextjs", "react", "tutorial"]
  }}
  text="Copy for AI"
  toastMessage="Copied!"
  position="inline" // 'inline' | 'fixed' | 'sticky'
  keyboardShortcut={true} // Ctrl/Cmd + Shift + C
/>

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | content | LLMContent | required | Content to copy | | text | string | 'Copy' | Button text | | position | 'inline' \| 'fixed' \| 'sticky' | 'inline' | Button positioning | | keyboardShortcut | boolean | true | Enable Ctrl/Cmd+Shift+C | | toastMessage | string | 'Copied!' | Success message | | toastDuration | number | 2000 | Toast display time (ms) | | disabled | boolean | false | Disable button | | className | string | '' | Additional CSS classes | | onCopy | (action: CopyAction) => void | - | Copy callback | | onError | (error: Error) => void | - | Error callback | | onAnalytics | (event: AnalyticsEvent) => void | - | Analytics callback |

CopyDropdown

Split button with dropdown menu for copy, view, and download options.

import { CopyDropdown } from '@seoengine.ai/next-llm-ready';

<CopyDropdown
  content={{
    title: "API Documentation",
    content: "<h2>Authentication</h2><p>Use Bearer tokens...</p>",
    url: "https://docs.example.com/api",
  }}
  text="Copy for AI"
  menuItems={[
    { id: 'copy', label: 'Copy to clipboard', action: 'copy', shortcut: '⌘+Shift+C' },
    { id: 'view', label: 'View markdown', action: 'view' },
    { id: 'download', label: 'Download .md file', action: 'download' },
  ]}
/>

Props

Extends CopyButtonProps with:

| Prop | Type | Default | Description | |------|------|---------|-------------| | menuItems | DropdownMenuItem[] | Default items | Menu options |

Menu Item

interface DropdownMenuItem {
  id: string;
  label: string;
  action: 'copy' | 'view' | 'download' | (() => void);
  icon?: React.ReactNode;
  shortcut?: string;
}

TOC

Sticky table of contents with active heading highlighting.

'use client';

import { useRef } from 'react';
import { TOC } from '@seoengine.ai/next-llm-ready';

export default function Article({ content }) {
  const contentRef = useRef<HTMLDivElement>(null);

  return (
    <div className="article-layout">
      <TOC
        contentRef={contentRef}
        title="On This Page"
        levels={['h2', 'h3']}
        sticky
        stickyOffset={80}
        position="right"
        highlightActive
        collapsible
      />
      <article ref={contentRef}>
        <h2 id="intro">Introduction</h2>
        <p>Content here...</p>
        <h3 id="setup">Setup</h3>
        <p>More content...</p>
      </article>
    </div>
  );
}

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | contentRef | RefObject<HTMLElement> | - | Reference to content container | | headings | TOCHeading[] | - | Manual headings (optional) | | title | string | 'On This Page' | TOC title | | levels | HeadingLevel[] | ['h2', 'h3', 'h4'] | Heading levels to include | | position | 'left' \| 'right' | 'right' | TOC position | | sticky | boolean | true | Enable sticky positioning | | stickyOffset | number | 80 | Top offset when sticky (px) | | smoothScroll | boolean | true | Smooth scroll on click | | highlightActive | boolean | true | Highlight active heading | | collapsible | boolean | true | Allow collapsing | | defaultCollapsed | boolean | false | Start collapsed |

TOCMobile

Mobile-optimized TOC with slide-up panel.

import { TOCMobile } from '@seoengine.ai/next-llm-ready';

// Same props as TOC
<TOCMobile contentRef={contentRef} />

LLMBadge

Visual indicator showing content is AI-ready.

import { LLMBadge } from '@seoengine.ai/next-llm-ready';

<LLMBadge
  text="AI Ready"
  size="md" // 'sm' | 'md' | 'lg'
  showTooltip
  tooltipContent="This content is optimized for AI assistants"
/>

Hooks

useLLMCopy

Core hook for implementing custom copy functionality.

'use client';

import { useLLMCopy } from '@seoengine.ai/next-llm-ready/hooks';

function CustomCopyButton({ content }) {
  const { copy, view, download, markdown, isCopying, isSuccess, error } = useLLMCopy({
    content,
    onSuccess: (action) => console.log(`${action} completed`),
    onError: (err) => console.error(err),
  });

  return (
    <div>
      <button onClick={copy} disabled={isCopying}>
        {isSuccess ? 'Copied!' : 'Copy'}
      </button>
      <button onClick={() => { view(); alert(markdown); }}>View</button>
      <button onClick={download}>Download</button>
    </div>
  );
}

Options

| Option | Type | Description | |--------|------|-------------| | content | LLMContent | Content to process | | onSuccess | (action: CopyAction) => void | Success callback | | onError | (error: Error) => void | Error callback | | onAnalytics | (event: AnalyticsEvent) => void | Analytics callback |

Returns

| Property | Type | Description | |----------|------|-------------| | copy | () => Promise<boolean> | Copy to clipboard | | view | () => string | Get markdown content | | download | () => void | Download as .md file | | markdown | string | Generated markdown | | isCopying | boolean | Copy in progress | | isSuccess | boolean | Copy succeeded | | error | Error \| null | Last error |

useTOC

Hook for building custom table of contents.

'use client';

import { useTOC } from '@seoengine.ai/next-llm-ready/hooks';

function CustomTOC({ contentRef }) {
  const { headings, activeId, scrollTo } = useTOC({
    contentRef,
    levels: ['h2', 'h3'],
  });

  return (
    <nav>
      {headings.map((heading) => (
        <a
          key={heading.id}
          href={`#${heading.id}`}
          onClick={(e) => {
            e.preventDefault();
            scrollTo(heading.id);
          }}
          style={{ fontWeight: activeId === heading.id ? 'bold' : 'normal' }}
        >
          {heading.text}
        </a>
      ))}
    </nav>
  );
}

Options

| Option | Type | Description | |--------|------|-------------| | contentRef | RefObject<HTMLElement> | Content container | | levels | HeadingLevel[] | Heading levels |

Returns

| Property | Type | Description | |----------|------|-------------| | headings | TOCHeading[] | Extracted headings | | activeId | string \| null | Current active heading | | scrollTo | (id: string) => void | Scroll to heading |

useAnalytics

Track user interactions with copy functionality.

'use client';

import { useAnalytics } from '@seoengine.ai/next-llm-ready/hooks';

function TrackedComponent() {
  const { track } = useAnalytics({
    endpoint: '/api/analytics',
    contentId: 'article-123',
  });

  return (
    <button onClick={() => track('copy')}>
      Copy
    </button>
  );
}

Server Utilities

generateMarkdown

Convert HTML content to clean Markdown.

import { generateMarkdown } from '@seoengine.ai/next-llm-ready/server';

const markdown = generateMarkdown({
  title: 'My Article',
  content: '<p>Hello <strong>world</strong>!</p>',
  url: 'https://example.com/article',
  author: 'John Doe',
  publishedAt: '2024-01-15',
});

// Output:
// # My Article
//
// **Source:** https://example.com/article
// **Author:** John Doe
// **Published:** 2024-01-15
//
// ---
//
// Hello **world**!

generateLLMsTxt

Generate an llms.txt file for your site.

import { generateLLMsTxt } from '@seoengine.ai/next-llm-ready/server';

const llmsTxt = generateLLMsTxt({
  siteName: 'My Documentation',
  description: 'API documentation and guides',
  baseUrl: 'https://docs.example.com',
  pages: [
    {
      title: 'Getting Started',
      url: '/getting-started',
      description: 'Quick start guide',
    },
    {
      title: 'API Reference',
      url: '/api-reference',
      description: 'Complete API documentation',
    },
  ],
});

API Handlers

/llms.txt Endpoint

Create an /llms.txt endpoint for AI crawlers.

App Router (Next.js 13+)

// app/llms.txt/route.ts
import { createLLMsTxtHandler } from '@seoengine.ai/next-llm-ready/api';
import { getAllPages } from '@/lib/content';

export const GET = createLLMsTxtHandler({
  siteName: 'My Documentation',
  description: 'Technical documentation and guides',
  getPages: async () => {
    const pages = await getAllPages();
    return pages.map((page) => ({
      title: page.title,
      url: page.slug,
      description: page.excerpt,
    }));
  },
});

Pages Router

// pages/llms.txt.ts
import { createLLMsTxtHandler } from '@seoengine.ai/next-llm-ready/api';
import type { NextApiRequest, NextApiResponse } from 'next';

const handler = createLLMsTxtHandler({
  siteName: 'My Site',
  getPages: async () => [
    { title: 'Home', url: '/', description: 'Welcome' },
  ],
});

export default async function llmsTxt(req: NextApiRequest, res: NextApiResponse) {
  const response = await handler(req as any);
  res.setHeader('Content-Type', 'text/plain');
  res.send(await response.text());
}

?llm=1 Query Parameter

Serve Markdown version of any page.

Middleware Approach

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  if (request.nextUrl.searchParams.get('llm') === '1') {
    // Rewrite to markdown endpoint
    const url = request.nextUrl.clone();
    url.pathname = `/api/markdown${url.pathname}`;
    return NextResponse.rewrite(url);
  }
}

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

API Route Handler

// app/api/markdown/[...slug]/route.ts
import { createMarkdownHandler } from '@seoengine.ai/next-llm-ready/api';
import { getPageBySlug } from '@/lib/content';

export const GET = createMarkdownHandler({
  getContent: async (slug) => {
    const page = await getPageBySlug(slug);
    if (!page) return null;

    return {
      title: page.title,
      content: page.content,
      url: `https://example.com/${slug}`,
      author: page.author,
      publishedAt: page.publishedAt,
    };
  },
});

Analytics

Track how users interact with your AI-ready content.

Client-Side Tracking

'use client';

import { CopyButton } from '@seoengine.ai/next-llm-ready';

<CopyButton
  content={content}
  onAnalytics={(event) => {
    // Send to your analytics provider
    gtag('event', 'llm_copy', {
      action: event.action,
      content_id: event.contentId,
    });
  }}
/>

Server-Side Analytics API

// app/api/llm-analytics/route.ts
import { createAnalyticsApiHandler } from '@seoengine.ai/next-llm-ready/api';

export const POST = createAnalyticsApiHandler({
  onEvent: async (event) => {
    // Store in database
    await db.analytics.create({
      data: {
        action: event.action,
        contentId: event.contentId,
        timestamp: new Date(),
      },
    });
  },
});

Theming

All components use CSS custom properties for easy theming.

Override Variables

:root {
  /* Primary colors */
  --llm-ready-primary: #0070f3;
  --llm-ready-primary-hover: #0060df;

  /* Text colors */
  --llm-ready-text: #1f2937;
  --llm-ready-text-muted: #6b7280;

  /* Backgrounds */
  --llm-ready-bg: #ffffff;
  --llm-ready-bg-secondary: #f9fafb;

  /* Borders */
  --llm-ready-border: #e5e7eb;
  --llm-ready-radius: 8px;

  /* Shadows */
  --llm-ready-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

Dark Mode

Automatic dark mode support via prefers-color-scheme, or use the .dark class:

<body className={isDark ? 'dark' : ''}>
  <CopyButton content={content} />
</body>

Tailwind CSS Integration

<CopyButton
  content={content}
  className="rounded-lg shadow-lg hover:shadow-xl"
/>

TypeScript

Full TypeScript support with exported types.

import type {
  LLMContent,
  CopyButtonProps,
  TOCHeading,
  AnalyticsEvent,
} from '@seoengine.ai/next-llm-ready';

const content: LLMContent = {
  title: 'My Article',
  content: '<p>Content...</p>',
  url: 'https://example.com/article',
};

Type Definitions

interface LLMContent {
  title: string;
  content: string;        // HTML content
  url?: string;           // Canonical URL
  author?: string;        // Author name
  publishedAt?: string;   // ISO date string
  modifiedAt?: string;    // ISO date string
  tags?: string[];        // Content tags
  categories?: string[];  // Content categories
  excerpt?: string;       // Short description
  metadata?: Record<string, unknown>;
}

interface TOCHeading {
  id: string;
  text: string;
  level: number;
  children?: TOCHeading[];
}

type CopyAction = 'copy' | 'view' | 'download';

interface AnalyticsEvent {
  action: CopyAction;
  contentId?: string;
  contentTitle?: string;
  timestamp: number;
}

Examples

Blog Post Page

// app/blog/[slug]/page.tsx
import { CopyDropdown, TOC, LLMBadge } from '@seoengine.ai/next-llm-ready';
import { getPost } from '@/lib/posts';

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);

  return (
    <div className="max-w-6xl mx-auto flex gap-8">
      {/* Sidebar TOC */}
      <aside className="hidden lg:block w-64">
        <TOC
          headings={post.headings}
          sticky
          stickyOffset={100}
        />
      </aside>

      {/* Main Content */}
      <article className="flex-1">
        <header className="mb-8">
          <div className="flex items-center gap-4 mb-4">
            <LLMBadge />
            <CopyDropdown
              content={{
                title: post.title,
                content: post.content,
                url: `https://example.com/blog/${params.slug}`,
                author: post.author,
                publishedAt: post.publishedAt,
              }}
              text="Copy for AI"
            />
          </div>
          <h1>{post.title}</h1>
        </header>

        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </article>
    </div>
  );
}

Documentation Site

// app/docs/[...slug]/page.tsx
import { CopyButton, TOC, TOCMobile } from '@seoengine.ai/next-llm-ready';
import { getDoc } from '@/lib/docs';

export default async function DocPage({ params }) {
  const doc = await getDoc(params.slug.join('/'));

  return (
    <>
      {/* Mobile TOC */}
      <TOCMobile headings={doc.headings} />

      <div className="lg:grid lg:grid-cols-[1fr_250px] gap-8">
        <article>
          <div className="flex items-center justify-between mb-6">
            <h1>{doc.title}</h1>
            <CopyButton
              content={{
                title: doc.title,
                content: doc.content,
                url: `https://docs.example.com/${params.slug.join('/')}`,
              }}
              text="Copy"
            />
          </div>
          <div dangerouslySetInnerHTML={{ __html: doc.content }} />
        </article>

        {/* Desktop TOC */}
        <aside className="hidden lg:block">
          <TOC headings={doc.headings} sticky />
        </aside>
      </div>
    </>
  );
}

Headless Implementation

Build completely custom UI with hooks:

'use client';

import { useLLMCopy, useTOC } from '@seoengine.ai/next-llm-ready/hooks';
import { motion, AnimatePresence } from 'framer-motion';

function CustomCopyButton({ content }) {
  const { copy, isCopying, isSuccess } = useLLMCopy({ content });

  return (
    <motion.button
      onClick={copy}
      disabled={isCopying}
      animate={{ scale: isSuccess ? [1, 1.1, 1] : 1 }}
    >
      <AnimatePresence mode="wait">
        {isSuccess ? (
          <motion.span key="success" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
            Copied!
          </motion.span>
        ) : (
          <motion.span key="copy" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
            Copy for AI
          </motion.span>
        )}
      </AnimatePresence>
    </motion.button>
  );
}

Browser Support

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+

Uses modern APIs:

  • Clipboard API (with fallback)
  • Intersection Observer
  • CSS Custom Properties

Contributing

Contributions are welcome! Please read our Contributing Guide for details.

# Clone the repository
git clone https://github.com/SEOengineai/next-llm-ready.git

# Install dependencies
pnpm install

# Run development
pnpm dev

# Run tests
pnpm test

# Build
pnpm build

License

MIT License - see LICENSE for details.


Related Projects


FAQ: Next.js SEO for AI

What is LLM SEO?

LLM SEO (Large Language Model SEO) is the practice of optimizing your content for AI assistants like ChatGPT, Claude, and Gemini. As more users discover content through AI-powered search, traditional SEO alone is insufficient.

How does next-llm-ready differ from next-seo?

next-seo focuses on traditional search engine optimization (meta tags, Open Graph, JSON-LD). next-llm-ready focuses on AI/LLM optimization (markdown export, llms.txt, copy functionality). Use both together for complete Next.js SEO coverage.

What is llms.txt?

The /llms.txt file is like robots.txt or sitemap.xml but for AI crawlers. It provides a structured list of your content that LLMs can easily parse and index. Learn more about the llms.txt specification.

Will this improve my Google rankings?

This package focuses on AI search (ChatGPT, Claude, Perplexity). For Google rankings, use next-seo. However, having AI-friendly content can indirectly boost your visibility as AI-powered search becomes more prevalent.

Can I use this with Next.js App Router?

Yes! next-llm-ready fully supports Next.js 13+ App Router and Server Components. It also works with Pages Router for older projects.


Keywords

Next.js SEO, next seo, next-seo alternative, LLM SEO, AI SEO, nextjs AI, next.js llm, ChatGPT SEO, Claude SEO, Gemini SEO, AI-ready content, llms.txt, markdown export, copy to AI, Next.js table of contents, React SEO, AI content optimization, LLM-friendly website, AI search optimization, next.js content, React LLM