@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.
Maintainers
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.txtand?llm=1for 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 medianext-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-readyImport 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 buildLicense
MIT License - see LICENSE for details.
Related Projects
- LLM Ready WordPress Plugin - Original WordPress plugin
- llms.txt Specification - Standard for AI-readable content
- next-seo - Traditional SEO for Next.js (use alongside this package)
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
