🚀 Built by SEO Engine — The AI-powered platform that helps you rank higher, drive more traffic, and convert visitors into customers. Try it free →
Make your Next.js content AI-ready in seconds.
Copy buttons, Table of Contents, `/llms.txt` endpoint, and `?llm=1` query support.
Large Language Models (LLMs) like ChatGPT, Claude, and Gemini 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
- LLM-optimized endpoints -
/llms.txtand?llm=1for AI crawlers - 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
npm install @seoengine.ai/next-llm-ready
# or
yarn add @seoengine.ai/next-llm-ready
# or
pnpm add @seoengine.ai/next-llm-ready// app/layout.tsx or _app.tsx
import '@seoengine.ai/next-llm-ready/styles.css';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.
| 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 |
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
/>| 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 |
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' },
]}
/>Extends CopyButtonProps with:
| Prop | Type | Default | Description |
|---|---|---|---|
menuItems |
DropdownMenuItem[] |
Default items | Menu options |
interface DropdownMenuItem {
id: string;
label: string;
action: 'copy' | 'view' | 'download' | (() => void);
icon?: React.ReactNode;
shortcut?: string;
}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>
);
}| 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 |
Mobile-optimized TOC with slide-up panel.
import { TOCMobile } from '@seoengine.ai/next-llm-ready';
// Same props as TOC
<TOCMobile contentRef={contentRef} />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"
/>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>
);
}| 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 |
| 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 |
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>
);
}| Option | Type | Description |
|---|---|---|
contentRef |
RefObject<HTMLElement> |
Content container |
levels |
HeadingLevel[] |
Heading levels |
| Property | Type | Description |
|---|---|---|
headings |
TOCHeading[] |
Extracted headings |
activeId |
string | null |
Current active heading |
scrollTo |
(id: string) => void |
Scroll to heading |
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>
);
}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**!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',
},
],
});Create an /llms.txt endpoint for AI crawlers.
// 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/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());
}Serve Markdown version of any page.
// 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*'],
};// 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,
};
},
});Track how users interact with your AI-ready content.
'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,
});
}}
/>// 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(),
},
});
},
});All components use CSS custom properties for easy theming.
: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);
}Automatic dark mode support via prefers-color-scheme, or use the .dark class:
<body className={isDark ? 'dark' : ''}>
<CopyButton content={content} />
</body><CopyButton
content={content}
className="rounded-lg shadow-lg hover:shadow-xl"
/>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',
};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;
}// 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>
);
}// 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>
</>
);
}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>
);
}- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Uses modern APIs:
- Clipboard API (with fallback)
- Intersection Observer
- CSS Custom Properties
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 buildMIT License - see LICENSE for details.
- LLM Ready WordPress Plugin - Original WordPress plugin
- llms.txt Specification - Standard for AI-readable content
Made with for the AI-first web