Core Concepts

Runtime Sync (Optional)

Last updated by Harlan Wilton in fix(cloudflare-pages): disable cron usage.

Most sites don't need this. Prerendering handles page indexing automatically. Runtime sync is only needed for sites with frequently changing content that can't be prerendered.

When You Need Runtime Sync

Enable runtime sync if your site has:

  • Dynamic pages generated at runtime (e.g., user-generated content)
  • Frequently updated content that changes between deploys
  • API-driven pages where content comes from external sources

If your content only changes on deploy, stick with prerendering—it's faster and simpler.

How It Works

┌─────────────────────────────────────────────────────────────┐
│ Default: Prerendering (source of truth)                     │
│   Build time → crawl sitemap → create SQLite → compress    │
│   Cold start → restore dump → llms.txt works immediately!  │
├─────────────────────────────────────────────────────────────┤
│ Opt-in: Runtime Sync (for dynamic content)                  │
│   Cold start → restore dump → sitemap seeder → poll        │
│   Cron (optional) → background re-indexing of stale pages  │
└─────────────────────────────────────────────────────────────┘

Enabling Runtime Sync

nuxt.config.ts
export default defineNuxtConfig({
  aiReady: {
    runtimeSync: {
      ttl: 3600, // Re-index pages & refresh sitemap older than 1 hour
      batchSize: 20, // Pages per batch
      pruneTtl: 0 // Prune routes not in sitemap (0 = never)
    },
    cron: true // Optional: scheduled background indexing (every minute)
  }
})
A runtimeSyncSecret is auto-generated when runtimeSync or cron is enabled. Set NUXT_AI_READY_RUNTIME_SYNC_SECRET env var to use your own.

Control Endpoints

When runtimeSync is enabled, these endpoints are available:

# Check indexing progress
GET /__ai-ready/status
# Returns: { total: 50, indexed: 45, pending: 5 }

# Trigger batch indexing
POST /__ai-ready/poll?secret=<token>
# Returns: { indexed: 20, remaining: 25, errors: [], duration: 1234, complete: false }

# Process all pending pages (with timeout)
POST /__ai-ready/poll?secret=<token>&all=true&timeout=30000
# Returns: { indexed: 45, remaining: 0, errors: [], duration: 28500, complete: true }

# Prune stale routes (dry run - preview what would be pruned)
POST /__ai-ready/prune?dry=true&ttl=604800
# Returns: { routes: ["/old-page"], count: 1, ttl: 604800, dry: true }

# Prune stale routes (execute)
POST /__ai-ready/prune?secret=<token>&ttl=604800
# Returns: { pruned: 1, ttl: 604800, dry: false }

The secret query param is required for POST endpoints if runtimeSync.secret is configured.

Poll Endpoint Options

ParamTypeDefaultDescription
limitnumber10Max pages per batch (max: 50)
allbooleanfalseProcess until complete
timeoutnumber30000Max ms for all mode
secretstringAuth token if configured

Prune Endpoint Options

ParamTypeDefaultDescription
drybooleanfalsePreview stale routes without deleting
ttlnumberpruneTtl configPrune routes older than this (seconds)
secretstringAuth token (required unless dry run)

Scheduled Indexing

When cron: true, automatic background indexing is enabled via Nitro scheduled tasks (runs every minute):

nuxt.config.ts
export default defineNuxtConfig({
  aiReady: {
    cron: true // Runs every minute, auto-enables runtimeSync
  }
})

The module auto-enables nitro.experimental.tasks when cron is configured.

Cloudflare Workers: Cron triggers are auto-configured in wrangler.toml.Cloudflare Pages: Does not support cron triggers. Use an external scheduler to call GET /__ai-ready/cron?secret=<token>. See the Cloudflare guide for details.

TTL Configuration

OptionDefaultDescription
ttl3600Re-index pages & refresh sitemap older than this (seconds)
pruneTtl0Delete routes not in sitemap for this long (0 = never)

Force re-index regardless of TTL:

await indexPage('/about', html, { force: true })

Database Configuration

The module uses SQLite via db0 for cross-platform support. It auto-detects the best connector:

RuntimeConnector
Bunbun:sqlite
Node.js 22.5+node:sqlite
Node.js <22.5better-sqlite3

For edge deployments, configure D1 or LibSQL:

export default defineNuxtConfig({
  aiReady: {
    database: {
      type: 'd1',
      bindingName: 'AI_READY_DB'
    }
  }
})

Serverless Cold Starts

For serverless platforms (Vercel, Netlify, Lambda), the database is ephemeral. The module handles this by:

  1. Build time: Creates SQLite database and compresses it to __ai-ready/pages.dump
  2. Cold start: db-restore plugin decompresses dump and imports into fresh database
  3. Runtime (if enabled): New pages indexed via sitemap seeder

Prerendered pages are always available, even on cold starts.

Sync with External Systems

Use the ai-ready:page:indexed hook to sync with vector databases, search indexes, or analytics:

server/plugins/embeddings.ts
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})`)
  })
})

Manual Indexing

Trigger indexing from API routes or plugins:

server/api/reindex.post.ts
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
  }
})

Direct Database Access

For advanced use cases, access the database directly:

server/api/search.ts
import { searchPages, useDatabase } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const { q } = getQuery(event)

  // FTS5 full-text search
  const results = await searchPages(event, q as string, { limit: 10 })

  return results
})

Available query functions:

FunctionDescription
queryPages(event, opts)Query pages with filters/pagination
searchPages(event, query, opts)FTS5 full-text search
countPages(event, opts)Count pages matching criteria
streamPages(event, opts)Stream pages for large datasets
upsertPage(event, page)Insert or update a page

IndexNow Integration

Combine runtime sync with IndexNow to notify search engines instantly when pages change. When both runtimeSync.cron and indexNow are enabled, IndexNow syncs automatically after each scheduled indexing run.

CLI

Use the nuxt-ai-ready CLI to interact with control endpoints:

# Check indexing status
npx nuxt-ai-ready status

# Trigger batch indexing
npx nuxt-ai-ready poll

# Process all pending pages
npx nuxt-ai-ready poll --all

# Preview stale routes (dry run)
npx nuxt-ai-ready prune --dry

# Prune stale routes
npx nuxt-ai-ready prune --ttl 604800

The CLI auto-authenticates using the generated secret. See the CLI guide for all commands and options.

Did this page help you?