@seoengine.ai/next-llm-ready 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +858 -0
- package/dist/api/index.cjs +624 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +295 -0
- package/dist/api/index.d.ts +295 -0
- package/dist/api/index.js +613 -0
- package/dist/api/index.js.map +1 -0
- package/dist/hooks/index.cjs +619 -0
- package/dist/hooks/index.cjs.map +1 -0
- package/dist/hooks/index.d.cts +257 -0
- package/dist/hooks/index.d.ts +257 -0
- package/dist/hooks/index.js +611 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.cjs +1609 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +850 -0
- package/dist/index.d.ts +850 -0
- package/dist/index.js +1576 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.cjs +398 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +134 -0
- package/dist/server/index.d.ts +134 -0
- package/dist/server/index.js +390 -0
- package/dist/server/index.js.map +1 -0
- package/dist/styles.css +855 -0
- package/package.json +118 -0
package/README.md
ADDED
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
# @seoengine.ai/next-llm-ready
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/@seoengine.ai/next-llm-ready.svg" alt="npm version" />
|
|
5
|
+
<img src="https://img.shields.io/npm/dm/@seoengine.ai/next-llm-ready.svg" alt="npm downloads" />
|
|
6
|
+
<img src="https://img.shields.io/github/license/SEOengineai/next-llm-ready.svg" alt="license" />
|
|
7
|
+
<img src="https://img.shields.io/badge/TypeScript-Ready-blue.svg" alt="TypeScript" />
|
|
8
|
+
<img src="https://img.shields.io/badge/Next.js-13%2B-black.svg" alt="Next.js" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<strong>Make your Next.js content AI-ready in seconds.</strong><br>
|
|
13
|
+
Copy buttons, Table of Contents, `/llms.txt` endpoint, and `?llm=1` query support.
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Why next-llm-ready?
|
|
19
|
+
|
|
20
|
+
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:
|
|
21
|
+
|
|
22
|
+
- **One-click copy** - Users can copy your content as clean Markdown
|
|
23
|
+
- **LLM-optimized endpoints** - `/llms.txt` and `?llm=1` for AI crawlers
|
|
24
|
+
- **Table of Contents** - Auto-generated navigation with active heading tracking
|
|
25
|
+
- **Analytics** - Track how users interact with your AI-ready content
|
|
26
|
+
- **Zero config** - Works out of the box with sensible defaults
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @seoengine.ai/next-llm-ready
|
|
34
|
+
# or
|
|
35
|
+
yarn add @seoengine.ai/next-llm-ready
|
|
36
|
+
# or
|
|
37
|
+
pnpm add @seoengine.ai/next-llm-ready
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Import Styles
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// app/layout.tsx or _app.tsx
|
|
44
|
+
import '@seoengine.ai/next-llm-ready/styles.css';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Add Copy Button
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { CopyButton } from '@seoengine.ai/next-llm-ready';
|
|
51
|
+
|
|
52
|
+
export default function Article({ title, content, url }) {
|
|
53
|
+
return (
|
|
54
|
+
<article>
|
|
55
|
+
<CopyButton
|
|
56
|
+
content={{ title, content, url }}
|
|
57
|
+
text="Copy for AI"
|
|
58
|
+
/>
|
|
59
|
+
<h1>{title}</h1>
|
|
60
|
+
<div dangerouslySetInnerHTML={{ __html: content }} />
|
|
61
|
+
</article>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**That's it!** Your content is now AI-ready.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
| Feature | Description |
|
|
73
|
+
|---------|-------------|
|
|
74
|
+
| [CopyButton](#copybutton) | Simple copy button with success feedback |
|
|
75
|
+
| [CopyDropdown](#copydropdown) | Split button with copy/view/download menu |
|
|
76
|
+
| [TOC](#toc) | Sticky table of contents with active tracking |
|
|
77
|
+
| [LLMBadge](#llmbadge) | Visual indicator that content is AI-ready |
|
|
78
|
+
| [useLLMCopy](#usellmcopy) | Hook for custom copy implementations |
|
|
79
|
+
| [useTOC](#usetoc) | Hook for custom TOC implementations |
|
|
80
|
+
| [/llms.txt](#llmstxt-endpoint) | Sitemap-like file for AI crawlers |
|
|
81
|
+
| [?llm=1](#llm1-query-parameter) | Markdown version of any page |
|
|
82
|
+
| [Analytics](#analytics) | Track copy events and engagement |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Components
|
|
87
|
+
|
|
88
|
+
### CopyButton
|
|
89
|
+
|
|
90
|
+
Simple button that copies content as Markdown to clipboard.
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { CopyButton } from '@seoengine.ai/next-llm-ready';
|
|
94
|
+
|
|
95
|
+
<CopyButton
|
|
96
|
+
content={{
|
|
97
|
+
title: "Getting Started with Next.js",
|
|
98
|
+
content: "<p>Next.js is a React framework...</p>",
|
|
99
|
+
url: "https://example.com/nextjs-guide",
|
|
100
|
+
author: "John Doe",
|
|
101
|
+
publishedAt: "2024-01-15",
|
|
102
|
+
tags: ["nextjs", "react", "tutorial"]
|
|
103
|
+
}}
|
|
104
|
+
text="Copy for AI"
|
|
105
|
+
toastMessage="Copied!"
|
|
106
|
+
position="inline" // 'inline' | 'fixed' | 'sticky'
|
|
107
|
+
keyboardShortcut={true} // Ctrl/Cmd + Shift + C
|
|
108
|
+
/>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Props
|
|
112
|
+
|
|
113
|
+
| Prop | Type | Default | Description |
|
|
114
|
+
|------|------|---------|-------------|
|
|
115
|
+
| `content` | `LLMContent` | required | Content to copy |
|
|
116
|
+
| `text` | `string` | `'Copy'` | Button text |
|
|
117
|
+
| `position` | `'inline' \| 'fixed' \| 'sticky'` | `'inline'` | Button positioning |
|
|
118
|
+
| `keyboardShortcut` | `boolean` | `true` | Enable Ctrl/Cmd+Shift+C |
|
|
119
|
+
| `toastMessage` | `string` | `'Copied!'` | Success message |
|
|
120
|
+
| `toastDuration` | `number` | `2000` | Toast display time (ms) |
|
|
121
|
+
| `disabled` | `boolean` | `false` | Disable button |
|
|
122
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
123
|
+
| `onCopy` | `(action: CopyAction) => void` | - | Copy callback |
|
|
124
|
+
| `onError` | `(error: Error) => void` | - | Error callback |
|
|
125
|
+
| `onAnalytics` | `(event: AnalyticsEvent) => void` | - | Analytics callback |
|
|
126
|
+
|
|
127
|
+
### CopyDropdown
|
|
128
|
+
|
|
129
|
+
Split button with dropdown menu for copy, view, and download options.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { CopyDropdown } from '@seoengine.ai/next-llm-ready';
|
|
133
|
+
|
|
134
|
+
<CopyDropdown
|
|
135
|
+
content={{
|
|
136
|
+
title: "API Documentation",
|
|
137
|
+
content: "<h2>Authentication</h2><p>Use Bearer tokens...</p>",
|
|
138
|
+
url: "https://docs.example.com/api",
|
|
139
|
+
}}
|
|
140
|
+
text="Copy for AI"
|
|
141
|
+
menuItems={[
|
|
142
|
+
{ id: 'copy', label: 'Copy to clipboard', action: 'copy', shortcut: '⌘+Shift+C' },
|
|
143
|
+
{ id: 'view', label: 'View markdown', action: 'view' },
|
|
144
|
+
{ id: 'download', label: 'Download .md file', action: 'download' },
|
|
145
|
+
]}
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Props
|
|
150
|
+
|
|
151
|
+
Extends `CopyButtonProps` with:
|
|
152
|
+
|
|
153
|
+
| Prop | Type | Default | Description |
|
|
154
|
+
|------|------|---------|-------------|
|
|
155
|
+
| `menuItems` | `DropdownMenuItem[]` | Default items | Menu options |
|
|
156
|
+
|
|
157
|
+
#### Menu Item
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
interface DropdownMenuItem {
|
|
161
|
+
id: string;
|
|
162
|
+
label: string;
|
|
163
|
+
action: 'copy' | 'view' | 'download' | (() => void);
|
|
164
|
+
icon?: React.ReactNode;
|
|
165
|
+
shortcut?: string;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### TOC
|
|
170
|
+
|
|
171
|
+
Sticky table of contents with active heading highlighting.
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
'use client';
|
|
175
|
+
|
|
176
|
+
import { useRef } from 'react';
|
|
177
|
+
import { TOC } from '@seoengine.ai/next-llm-ready';
|
|
178
|
+
|
|
179
|
+
export default function Article({ content }) {
|
|
180
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="article-layout">
|
|
184
|
+
<TOC
|
|
185
|
+
contentRef={contentRef}
|
|
186
|
+
title="On This Page"
|
|
187
|
+
levels={['h2', 'h3']}
|
|
188
|
+
sticky
|
|
189
|
+
stickyOffset={80}
|
|
190
|
+
position="right"
|
|
191
|
+
highlightActive
|
|
192
|
+
collapsible
|
|
193
|
+
/>
|
|
194
|
+
<article ref={contentRef}>
|
|
195
|
+
<h2 id="intro">Introduction</h2>
|
|
196
|
+
<p>Content here...</p>
|
|
197
|
+
<h3 id="setup">Setup</h3>
|
|
198
|
+
<p>More content...</p>
|
|
199
|
+
</article>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Props
|
|
206
|
+
|
|
207
|
+
| Prop | Type | Default | Description |
|
|
208
|
+
|------|------|---------|-------------|
|
|
209
|
+
| `contentRef` | `RefObject<HTMLElement>` | - | Reference to content container |
|
|
210
|
+
| `headings` | `TOCHeading[]` | - | Manual headings (optional) |
|
|
211
|
+
| `title` | `string` | `'On This Page'` | TOC title |
|
|
212
|
+
| `levels` | `HeadingLevel[]` | `['h2', 'h3', 'h4']` | Heading levels to include |
|
|
213
|
+
| `position` | `'left' \| 'right'` | `'right'` | TOC position |
|
|
214
|
+
| `sticky` | `boolean` | `true` | Enable sticky positioning |
|
|
215
|
+
| `stickyOffset` | `number` | `80` | Top offset when sticky (px) |
|
|
216
|
+
| `smoothScroll` | `boolean` | `true` | Smooth scroll on click |
|
|
217
|
+
| `highlightActive` | `boolean` | `true` | Highlight active heading |
|
|
218
|
+
| `collapsible` | `boolean` | `true` | Allow collapsing |
|
|
219
|
+
| `defaultCollapsed` | `boolean` | `false` | Start collapsed |
|
|
220
|
+
|
|
221
|
+
### TOCMobile
|
|
222
|
+
|
|
223
|
+
Mobile-optimized TOC with slide-up panel.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { TOCMobile } from '@seoengine.ai/next-llm-ready';
|
|
227
|
+
|
|
228
|
+
// Same props as TOC
|
|
229
|
+
<TOCMobile contentRef={contentRef} />
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### LLMBadge
|
|
233
|
+
|
|
234
|
+
Visual indicator showing content is AI-ready.
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { LLMBadge } from '@seoengine.ai/next-llm-ready';
|
|
238
|
+
|
|
239
|
+
<LLMBadge
|
|
240
|
+
text="AI Ready"
|
|
241
|
+
size="md" // 'sm' | 'md' | 'lg'
|
|
242
|
+
showTooltip
|
|
243
|
+
tooltipContent="This content is optimized for AI assistants"
|
|
244
|
+
/>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Hooks
|
|
250
|
+
|
|
251
|
+
### useLLMCopy
|
|
252
|
+
|
|
253
|
+
Core hook for implementing custom copy functionality.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
'use client';
|
|
257
|
+
|
|
258
|
+
import { useLLMCopy } from '@seoengine.ai/next-llm-ready/hooks';
|
|
259
|
+
|
|
260
|
+
function CustomCopyButton({ content }) {
|
|
261
|
+
const { copy, view, download, markdown, isCopying, isSuccess, error } = useLLMCopy({
|
|
262
|
+
content,
|
|
263
|
+
onSuccess: (action) => console.log(`${action} completed`),
|
|
264
|
+
onError: (err) => console.error(err),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
<button onClick={copy} disabled={isCopying}>
|
|
270
|
+
{isSuccess ? 'Copied!' : 'Copy'}
|
|
271
|
+
</button>
|
|
272
|
+
<button onClick={() => { view(); alert(markdown); }}>View</button>
|
|
273
|
+
<button onClick={download}>Download</button>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Options
|
|
280
|
+
|
|
281
|
+
| Option | Type | Description |
|
|
282
|
+
|--------|------|-------------|
|
|
283
|
+
| `content` | `LLMContent` | Content to process |
|
|
284
|
+
| `onSuccess` | `(action: CopyAction) => void` | Success callback |
|
|
285
|
+
| `onError` | `(error: Error) => void` | Error callback |
|
|
286
|
+
| `onAnalytics` | `(event: AnalyticsEvent) => void` | Analytics callback |
|
|
287
|
+
|
|
288
|
+
#### Returns
|
|
289
|
+
|
|
290
|
+
| Property | Type | Description |
|
|
291
|
+
|----------|------|-------------|
|
|
292
|
+
| `copy` | `() => Promise<boolean>` | Copy to clipboard |
|
|
293
|
+
| `view` | `() => string` | Get markdown content |
|
|
294
|
+
| `download` | `() => void` | Download as .md file |
|
|
295
|
+
| `markdown` | `string` | Generated markdown |
|
|
296
|
+
| `isCopying` | `boolean` | Copy in progress |
|
|
297
|
+
| `isSuccess` | `boolean` | Copy succeeded |
|
|
298
|
+
| `error` | `Error \| null` | Last error |
|
|
299
|
+
|
|
300
|
+
### useTOC
|
|
301
|
+
|
|
302
|
+
Hook for building custom table of contents.
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
'use client';
|
|
306
|
+
|
|
307
|
+
import { useTOC } from '@seoengine.ai/next-llm-ready/hooks';
|
|
308
|
+
|
|
309
|
+
function CustomTOC({ contentRef }) {
|
|
310
|
+
const { headings, activeId, scrollTo } = useTOC({
|
|
311
|
+
contentRef,
|
|
312
|
+
levels: ['h2', 'h3'],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<nav>
|
|
317
|
+
{headings.map((heading) => (
|
|
318
|
+
<a
|
|
319
|
+
key={heading.id}
|
|
320
|
+
href={`#${heading.id}`}
|
|
321
|
+
onClick={(e) => {
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
scrollTo(heading.id);
|
|
324
|
+
}}
|
|
325
|
+
style={{ fontWeight: activeId === heading.id ? 'bold' : 'normal' }}
|
|
326
|
+
>
|
|
327
|
+
{heading.text}
|
|
328
|
+
</a>
|
|
329
|
+
))}
|
|
330
|
+
</nav>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### Options
|
|
336
|
+
|
|
337
|
+
| Option | Type | Description |
|
|
338
|
+
|--------|------|-------------|
|
|
339
|
+
| `contentRef` | `RefObject<HTMLElement>` | Content container |
|
|
340
|
+
| `levels` | `HeadingLevel[]` | Heading levels |
|
|
341
|
+
|
|
342
|
+
#### Returns
|
|
343
|
+
|
|
344
|
+
| Property | Type | Description |
|
|
345
|
+
|----------|------|-------------|
|
|
346
|
+
| `headings` | `TOCHeading[]` | Extracted headings |
|
|
347
|
+
| `activeId` | `string \| null` | Current active heading |
|
|
348
|
+
| `scrollTo` | `(id: string) => void` | Scroll to heading |
|
|
349
|
+
|
|
350
|
+
### useAnalytics
|
|
351
|
+
|
|
352
|
+
Track user interactions with copy functionality.
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
'use client';
|
|
356
|
+
|
|
357
|
+
import { useAnalytics } from '@seoengine.ai/next-llm-ready/hooks';
|
|
358
|
+
|
|
359
|
+
function TrackedComponent() {
|
|
360
|
+
const { track } = useAnalytics({
|
|
361
|
+
endpoint: '/api/analytics',
|
|
362
|
+
contentId: 'article-123',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return (
|
|
366
|
+
<button onClick={() => track('copy')}>
|
|
367
|
+
Copy
|
|
368
|
+
</button>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Server Utilities
|
|
376
|
+
|
|
377
|
+
### generateMarkdown
|
|
378
|
+
|
|
379
|
+
Convert HTML content to clean Markdown.
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { generateMarkdown } from '@seoengine.ai/next-llm-ready/server';
|
|
383
|
+
|
|
384
|
+
const markdown = generateMarkdown({
|
|
385
|
+
title: 'My Article',
|
|
386
|
+
content: '<p>Hello <strong>world</strong>!</p>',
|
|
387
|
+
url: 'https://example.com/article',
|
|
388
|
+
author: 'John Doe',
|
|
389
|
+
publishedAt: '2024-01-15',
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Output:
|
|
393
|
+
// # My Article
|
|
394
|
+
//
|
|
395
|
+
// **Source:** https://example.com/article
|
|
396
|
+
// **Author:** John Doe
|
|
397
|
+
// **Published:** 2024-01-15
|
|
398
|
+
//
|
|
399
|
+
// ---
|
|
400
|
+
//
|
|
401
|
+
// Hello **world**!
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### generateLLMsTxt
|
|
405
|
+
|
|
406
|
+
Generate an llms.txt file for your site.
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
import { generateLLMsTxt } from '@seoengine.ai/next-llm-ready/server';
|
|
410
|
+
|
|
411
|
+
const llmsTxt = generateLLMsTxt({
|
|
412
|
+
siteName: 'My Documentation',
|
|
413
|
+
description: 'API documentation and guides',
|
|
414
|
+
baseUrl: 'https://docs.example.com',
|
|
415
|
+
pages: [
|
|
416
|
+
{
|
|
417
|
+
title: 'Getting Started',
|
|
418
|
+
url: '/getting-started',
|
|
419
|
+
description: 'Quick start guide',
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
title: 'API Reference',
|
|
423
|
+
url: '/api-reference',
|
|
424
|
+
description: 'Complete API documentation',
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## API Handlers
|
|
433
|
+
|
|
434
|
+
### /llms.txt Endpoint
|
|
435
|
+
|
|
436
|
+
Create an `/llms.txt` endpoint for AI crawlers.
|
|
437
|
+
|
|
438
|
+
#### App Router (Next.js 13+)
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
// app/llms.txt/route.ts
|
|
442
|
+
import { createLLMsTxtHandler } from '@seoengine.ai/next-llm-ready/api';
|
|
443
|
+
import { getAllPages } from '@/lib/content';
|
|
444
|
+
|
|
445
|
+
export const GET = createLLMsTxtHandler({
|
|
446
|
+
siteName: 'My Documentation',
|
|
447
|
+
description: 'Technical documentation and guides',
|
|
448
|
+
getPages: async () => {
|
|
449
|
+
const pages = await getAllPages();
|
|
450
|
+
return pages.map((page) => ({
|
|
451
|
+
title: page.title,
|
|
452
|
+
url: page.slug,
|
|
453
|
+
description: page.excerpt,
|
|
454
|
+
}));
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### Pages Router
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
// pages/llms.txt.ts
|
|
463
|
+
import { createLLMsTxtHandler } from '@seoengine.ai/next-llm-ready/api';
|
|
464
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
465
|
+
|
|
466
|
+
const handler = createLLMsTxtHandler({
|
|
467
|
+
siteName: 'My Site',
|
|
468
|
+
getPages: async () => [
|
|
469
|
+
{ title: 'Home', url: '/', description: 'Welcome' },
|
|
470
|
+
],
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
export default async function llmsTxt(req: NextApiRequest, res: NextApiResponse) {
|
|
474
|
+
const response = await handler(req as any);
|
|
475
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
476
|
+
res.send(await response.text());
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### ?llm=1 Query Parameter
|
|
481
|
+
|
|
482
|
+
Serve Markdown version of any page.
|
|
483
|
+
|
|
484
|
+
#### Middleware Approach
|
|
485
|
+
|
|
486
|
+
```tsx
|
|
487
|
+
// middleware.ts
|
|
488
|
+
import { NextResponse } from 'next/server';
|
|
489
|
+
import type { NextRequest } from 'next/server';
|
|
490
|
+
|
|
491
|
+
export function middleware(request: NextRequest) {
|
|
492
|
+
if (request.nextUrl.searchParams.get('llm') === '1') {
|
|
493
|
+
// Rewrite to markdown endpoint
|
|
494
|
+
const url = request.nextUrl.clone();
|
|
495
|
+
url.pathname = `/api/markdown${url.pathname}`;
|
|
496
|
+
return NextResponse.rewrite(url);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export const config = {
|
|
501
|
+
matcher: ['/blog/:path*', '/docs/:path*'],
|
|
502
|
+
};
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### API Route Handler
|
|
506
|
+
|
|
507
|
+
```tsx
|
|
508
|
+
// app/api/markdown/[...slug]/route.ts
|
|
509
|
+
import { createMarkdownHandler } from '@seoengine.ai/next-llm-ready/api';
|
|
510
|
+
import { getPageBySlug } from '@/lib/content';
|
|
511
|
+
|
|
512
|
+
export const GET = createMarkdownHandler({
|
|
513
|
+
getContent: async (slug) => {
|
|
514
|
+
const page = await getPageBySlug(slug);
|
|
515
|
+
if (!page) return null;
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
title: page.title,
|
|
519
|
+
content: page.content,
|
|
520
|
+
url: `https://example.com/${slug}`,
|
|
521
|
+
author: page.author,
|
|
522
|
+
publishedAt: page.publishedAt,
|
|
523
|
+
};
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Analytics
|
|
531
|
+
|
|
532
|
+
Track how users interact with your AI-ready content.
|
|
533
|
+
|
|
534
|
+
### Client-Side Tracking
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
'use client';
|
|
538
|
+
|
|
539
|
+
import { CopyButton } from '@seoengine.ai/next-llm-ready';
|
|
540
|
+
|
|
541
|
+
<CopyButton
|
|
542
|
+
content={content}
|
|
543
|
+
onAnalytics={(event) => {
|
|
544
|
+
// Send to your analytics provider
|
|
545
|
+
gtag('event', 'llm_copy', {
|
|
546
|
+
action: event.action,
|
|
547
|
+
content_id: event.contentId,
|
|
548
|
+
});
|
|
549
|
+
}}
|
|
550
|
+
/>
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Server-Side Analytics API
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
// app/api/llm-analytics/route.ts
|
|
557
|
+
import { createAnalyticsApiHandler } from '@seoengine.ai/next-llm-ready/api';
|
|
558
|
+
|
|
559
|
+
export const POST = createAnalyticsApiHandler({
|
|
560
|
+
onEvent: async (event) => {
|
|
561
|
+
// Store in database
|
|
562
|
+
await db.analytics.create({
|
|
563
|
+
data: {
|
|
564
|
+
action: event.action,
|
|
565
|
+
contentId: event.contentId,
|
|
566
|
+
timestamp: new Date(),
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
},
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Theming
|
|
576
|
+
|
|
577
|
+
All components use CSS custom properties for easy theming.
|
|
578
|
+
|
|
579
|
+
### Override Variables
|
|
580
|
+
|
|
581
|
+
```css
|
|
582
|
+
:root {
|
|
583
|
+
/* Primary colors */
|
|
584
|
+
--llm-ready-primary: #0070f3;
|
|
585
|
+
--llm-ready-primary-hover: #0060df;
|
|
586
|
+
|
|
587
|
+
/* Text colors */
|
|
588
|
+
--llm-ready-text: #1f2937;
|
|
589
|
+
--llm-ready-text-muted: #6b7280;
|
|
590
|
+
|
|
591
|
+
/* Backgrounds */
|
|
592
|
+
--llm-ready-bg: #ffffff;
|
|
593
|
+
--llm-ready-bg-secondary: #f9fafb;
|
|
594
|
+
|
|
595
|
+
/* Borders */
|
|
596
|
+
--llm-ready-border: #e5e7eb;
|
|
597
|
+
--llm-ready-radius: 8px;
|
|
598
|
+
|
|
599
|
+
/* Shadows */
|
|
600
|
+
--llm-ready-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
601
|
+
}
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Dark Mode
|
|
605
|
+
|
|
606
|
+
Automatic dark mode support via `prefers-color-scheme`, or use the `.dark` class:
|
|
607
|
+
|
|
608
|
+
```tsx
|
|
609
|
+
<body className={isDark ? 'dark' : ''}>
|
|
610
|
+
<CopyButton content={content} />
|
|
611
|
+
</body>
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Tailwind CSS Integration
|
|
615
|
+
|
|
616
|
+
```tsx
|
|
617
|
+
<CopyButton
|
|
618
|
+
content={content}
|
|
619
|
+
className="rounded-lg shadow-lg hover:shadow-xl"
|
|
620
|
+
/>
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## TypeScript
|
|
626
|
+
|
|
627
|
+
Full TypeScript support with exported types.
|
|
628
|
+
|
|
629
|
+
```tsx
|
|
630
|
+
import type {
|
|
631
|
+
LLMContent,
|
|
632
|
+
CopyButtonProps,
|
|
633
|
+
TOCHeading,
|
|
634
|
+
AnalyticsEvent,
|
|
635
|
+
} from '@seoengine.ai/next-llm-ready';
|
|
636
|
+
|
|
637
|
+
const content: LLMContent = {
|
|
638
|
+
title: 'My Article',
|
|
639
|
+
content: '<p>Content...</p>',
|
|
640
|
+
url: 'https://example.com/article',
|
|
641
|
+
};
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
### Type Definitions
|
|
645
|
+
|
|
646
|
+
```tsx
|
|
647
|
+
interface LLMContent {
|
|
648
|
+
title: string;
|
|
649
|
+
content: string; // HTML content
|
|
650
|
+
url?: string; // Canonical URL
|
|
651
|
+
author?: string; // Author name
|
|
652
|
+
publishedAt?: string; // ISO date string
|
|
653
|
+
modifiedAt?: string; // ISO date string
|
|
654
|
+
tags?: string[]; // Content tags
|
|
655
|
+
categories?: string[]; // Content categories
|
|
656
|
+
excerpt?: string; // Short description
|
|
657
|
+
metadata?: Record<string, unknown>;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
interface TOCHeading {
|
|
661
|
+
id: string;
|
|
662
|
+
text: string;
|
|
663
|
+
level: number;
|
|
664
|
+
children?: TOCHeading[];
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
type CopyAction = 'copy' | 'view' | 'download';
|
|
668
|
+
|
|
669
|
+
interface AnalyticsEvent {
|
|
670
|
+
action: CopyAction;
|
|
671
|
+
contentId?: string;
|
|
672
|
+
contentTitle?: string;
|
|
673
|
+
timestamp: number;
|
|
674
|
+
}
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## Examples
|
|
680
|
+
|
|
681
|
+
### Blog Post Page
|
|
682
|
+
|
|
683
|
+
```tsx
|
|
684
|
+
// app/blog/[slug]/page.tsx
|
|
685
|
+
import { CopyDropdown, TOC, LLMBadge } from '@seoengine.ai/next-llm-ready';
|
|
686
|
+
import { getPost } from '@/lib/posts';
|
|
687
|
+
|
|
688
|
+
export default async function BlogPost({ params }) {
|
|
689
|
+
const post = await getPost(params.slug);
|
|
690
|
+
|
|
691
|
+
return (
|
|
692
|
+
<div className="max-w-6xl mx-auto flex gap-8">
|
|
693
|
+
{/* Sidebar TOC */}
|
|
694
|
+
<aside className="hidden lg:block w-64">
|
|
695
|
+
<TOC
|
|
696
|
+
headings={post.headings}
|
|
697
|
+
sticky
|
|
698
|
+
stickyOffset={100}
|
|
699
|
+
/>
|
|
700
|
+
</aside>
|
|
701
|
+
|
|
702
|
+
{/* Main Content */}
|
|
703
|
+
<article className="flex-1">
|
|
704
|
+
<header className="mb-8">
|
|
705
|
+
<div className="flex items-center gap-4 mb-4">
|
|
706
|
+
<LLMBadge />
|
|
707
|
+
<CopyDropdown
|
|
708
|
+
content={{
|
|
709
|
+
title: post.title,
|
|
710
|
+
content: post.content,
|
|
711
|
+
url: `https://example.com/blog/${params.slug}`,
|
|
712
|
+
author: post.author,
|
|
713
|
+
publishedAt: post.publishedAt,
|
|
714
|
+
}}
|
|
715
|
+
text="Copy for AI"
|
|
716
|
+
/>
|
|
717
|
+
</div>
|
|
718
|
+
<h1>{post.title}</h1>
|
|
719
|
+
</header>
|
|
720
|
+
|
|
721
|
+
<div dangerouslySetInnerHTML={{ __html: post.content }} />
|
|
722
|
+
</article>
|
|
723
|
+
</div>
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Documentation Site
|
|
729
|
+
|
|
730
|
+
```tsx
|
|
731
|
+
// app/docs/[...slug]/page.tsx
|
|
732
|
+
import { CopyButton, TOC, TOCMobile } from '@seoengine.ai/next-llm-ready';
|
|
733
|
+
import { getDoc } from '@/lib/docs';
|
|
734
|
+
|
|
735
|
+
export default async function DocPage({ params }) {
|
|
736
|
+
const doc = await getDoc(params.slug.join('/'));
|
|
737
|
+
|
|
738
|
+
return (
|
|
739
|
+
<>
|
|
740
|
+
{/* Mobile TOC */}
|
|
741
|
+
<TOCMobile headings={doc.headings} />
|
|
742
|
+
|
|
743
|
+
<div className="lg:grid lg:grid-cols-[1fr_250px] gap-8">
|
|
744
|
+
<article>
|
|
745
|
+
<div className="flex items-center justify-between mb-6">
|
|
746
|
+
<h1>{doc.title}</h1>
|
|
747
|
+
<CopyButton
|
|
748
|
+
content={{
|
|
749
|
+
title: doc.title,
|
|
750
|
+
content: doc.content,
|
|
751
|
+
url: `https://docs.example.com/${params.slug.join('/')}`,
|
|
752
|
+
}}
|
|
753
|
+
text="Copy"
|
|
754
|
+
/>
|
|
755
|
+
</div>
|
|
756
|
+
<div dangerouslySetInnerHTML={{ __html: doc.content }} />
|
|
757
|
+
</article>
|
|
758
|
+
|
|
759
|
+
{/* Desktop TOC */}
|
|
760
|
+
<aside className="hidden lg:block">
|
|
761
|
+
<TOC headings={doc.headings} sticky />
|
|
762
|
+
</aside>
|
|
763
|
+
</div>
|
|
764
|
+
</>
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Headless Implementation
|
|
770
|
+
|
|
771
|
+
Build completely custom UI with hooks:
|
|
772
|
+
|
|
773
|
+
```tsx
|
|
774
|
+
'use client';
|
|
775
|
+
|
|
776
|
+
import { useLLMCopy, useTOC } from '@seoengine.ai/next-llm-ready/hooks';
|
|
777
|
+
import { motion, AnimatePresence } from 'framer-motion';
|
|
778
|
+
|
|
779
|
+
function CustomCopyButton({ content }) {
|
|
780
|
+
const { copy, isCopying, isSuccess } = useLLMCopy({ content });
|
|
781
|
+
|
|
782
|
+
return (
|
|
783
|
+
<motion.button
|
|
784
|
+
onClick={copy}
|
|
785
|
+
disabled={isCopying}
|
|
786
|
+
animate={{ scale: isSuccess ? [1, 1.1, 1] : 1 }}
|
|
787
|
+
>
|
|
788
|
+
<AnimatePresence mode="wait">
|
|
789
|
+
{isSuccess ? (
|
|
790
|
+
<motion.span key="success" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
|
791
|
+
Copied!
|
|
792
|
+
</motion.span>
|
|
793
|
+
) : (
|
|
794
|
+
<motion.span key="copy" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
|
795
|
+
Copy for AI
|
|
796
|
+
</motion.span>
|
|
797
|
+
)}
|
|
798
|
+
</AnimatePresence>
|
|
799
|
+
</motion.button>
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
## Browser Support
|
|
807
|
+
|
|
808
|
+
- Chrome 90+
|
|
809
|
+
- Firefox 88+
|
|
810
|
+
- Safari 14+
|
|
811
|
+
- Edge 90+
|
|
812
|
+
|
|
813
|
+
Uses modern APIs:
|
|
814
|
+
- Clipboard API (with fallback)
|
|
815
|
+
- Intersection Observer
|
|
816
|
+
- CSS Custom Properties
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## Contributing
|
|
821
|
+
|
|
822
|
+
Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
823
|
+
|
|
824
|
+
```bash
|
|
825
|
+
# Clone the repository
|
|
826
|
+
git clone https://github.com/SEOengineai/next-llm-ready.git
|
|
827
|
+
|
|
828
|
+
# Install dependencies
|
|
829
|
+
pnpm install
|
|
830
|
+
|
|
831
|
+
# Run development
|
|
832
|
+
pnpm dev
|
|
833
|
+
|
|
834
|
+
# Run tests
|
|
835
|
+
pnpm test
|
|
836
|
+
|
|
837
|
+
# Build
|
|
838
|
+
pnpm build
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## License
|
|
844
|
+
|
|
845
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## Related Projects
|
|
850
|
+
|
|
851
|
+
- [LLM Ready WordPress Plugin](https://wordpress.org/plugins/llm-ready/) - Original WordPress plugin
|
|
852
|
+
- [llms.txt Specification](https://llmstxt.org/) - Standard for AI-readable content
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
<p align="center">
|
|
857
|
+
Made with for the AI-first web
|
|
858
|
+
</p>
|