By default, nuxt-ai-ready indexes pages during prerendering (nuxi generate). Runtime indexing enables on-demand indexing for SSR-only deployments or dynamic content.
export default defineNuxtConfig({
aiReady: {
runtimeIndexing: {
enabled: true,
storage: 'ai-ready',
ttl: 3600 // re-index after 1 hour
}
}
})
┌─────────────────────────────────────────────────────────────┐
│ 1. Server Start │
│ storage-init plugin loads prerendered pages.json │
│ into unstorage (if exists) │
├─────────────────────────────────────────────────────────────┤
│ 2. Page Visit │
│ afterResponse hook triggers │
│ waitUntil() runs HTML→markdown in background │
│ Page stored in unstorage │
├─────────────────────────────────────────────────────────────┤
│ 3. Hook Fires │
│ ai-ready:page:indexed called │
│ Integrate with embeddings, search, etc. │
├─────────────────────────────────────────────────────────────┤
│ 4. Data Access │
│ getPages() / getPagesList() read from storage │
│ MCP tools and llms.txt use indexed data │
└─────────────────────────────────────────────────────────────┘
Memory storage works for development but is ephemeral on serverless. Configure a persistent driver:
export default defineNuxtConfig({
aiReady: {
runtimeIndexing: { enabled: true }
},
nitro: {
storage: {
'ai-ready': {
driver: 'cloudflare-kv-binding',
binding: 'AI_READY_KV'
}
}
}
})
export default defineNuxtConfig({
aiReady: {
runtimeIndexing: { enabled: true }
},
nitro: {
storage: {
'ai-ready': { driver: 'vercel-kv' }
}
}
})
export default defineNuxtConfig({
aiReady: {
runtimeIndexing: { enabled: true }
},
nitro: {
storage: {
'ai-ready': {
driver: 'redis',
url: process.env.REDIS_URL
}
}
}
})
See Nitro Storage for all drivers.
Use the ai-ready:page:indexed hook to sync with vector databases, search indexes, or analytics:
export default defineNitroPlugin((nitro) => {
nitro.hooks.hook('ai-ready:page:indexed', async (ctx) => {
// Generate embeddings for vector search
const embedding = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: ctx.markdown
})
await vectorDb.upsert({
id: ctx.route,
vector: embedding.data[0].embedding,
metadata: {
title: ctx.title,
description: ctx.description,
route: ctx.route
}
})
console.log(`Indexed ${ctx.route} (update: ${ctx.isUpdate})`)
})
})
Trigger indexing from API routes or plugins:
import { indexPageByRoute } from '#ai-ready'
export default defineEventHandler(async (event) => {
const { paths } = await readBody(event)
const results = await Promise.all(
paths.map((path: string) =>
indexPageByRoute(path, event, { force: true })
)
)
return {
indexed: results.filter(r => r.success && !r.skipped).length,
skipped: results.filter(r => r.skipped).length,
failed: results.filter(r => !r.success).length
}
})
Pre-index important pages when the server starts:
import { indexPageByRoute } from '#ai-ready'
export default defineNitroPlugin(async () => {
// Skip during prerender
if (import.meta.prerender) return
const criticalPages = ['/', '/docs', '/pricing', '/features']
await Promise.all(
criticalPages.map(route => indexPageByRoute(route))
)
console.log(`Warmed ${criticalPages.length} pages`)
})
The ttl option controls how often pages are re-indexed:
| TTL Value | Behavior |
|---|---|
0 | Never re-index (index once per server lifetime) |
3600 | Re-index if page was indexed >1 hour ago |
86400 | Re-index daily |
aiReady: {
runtimeIndexing: {
enabled: true,
ttl: 3600 // Check freshness on each visit
}
}
Force re-index regardless of TTL:
await indexPage('/about', html, { force: true })
Combine prerendering with runtime indexing for best of both worlds:
export default defineNuxtConfig({
aiReady: {
runtimeIndexing: {
enabled: true,
ttl: 3600
}
},
nitro: {
prerender: {
routes: ['/', '/about', '/docs'] // Static pages
}
}
})
Prerendered pages are loaded into storage on startup, and new SSR pages are added as visitors access them.