---
title: "Runtime Sync (Optional) · Nuxt AI Ready · Nuxt SEO"
canonical_url: "https://nuxtseo.com/docs/ai-ready/guides/runtime-indexing"
last_updated: "2026-08-16T09:50:32.587Z"
meta:
  description: "Opt-in runtime page indexing for sites with dynamic content."
  "og:description": "Opt-in runtime page indexing for sites with dynamic content."
  "og:title": "Runtime Sync (Optional) · Nuxt AI Ready · Nuxt SEO"
---

Nuxt SEO on GitHub

Switch to AI ReadySwitch to Nuxt SEOSwitch to RobotsSwitch to SitemapSwitch to OG ImageSwitch to Schema.orgSwitch to Link CheckerSwitch to SEO UtilsSwitch to Site ConfigSwitch to Skew Protection

**Core Concepts**

# **Runtime Sync (Optional)**

**Most sites don't need this.** Prerendering handles page indexing automatically. Use runtime sync only for sites with frequently changing content that you can't prerender.

## 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

```text
┌─────────────────────────────────────────────────────────────┐
│ 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

```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)
  }
})
```

The module auto-generates a `**runtimeSyncSecret**` when you enable `**runtimeSync**` or `**cron**`. Set `**NUXT_AI_READY_RUNTIME_SYNC_SECRET**` env var to use your own.

## Control Endpoints

When you enable `**runtimeSync**`, these endpoints become available:

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

# Trigger batch indexing (requires Authorization: Bearer <token> header if secret configured)
POST /__ai-ready/poll
# Returns: { indexed: 20, remaining: 25, errors: [], duration: 1234, complete: false }

# Process all pending pages (with timeout)
POST /__ai-ready/poll?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, requires Authorization: Bearer <token> header)
POST /__ai-ready/prune?ttl=604800
# Returns: { pruned: 1, ttl: 604800, dry: false }
```

You must provide the `**Authorization: Bearer <token>**` header for POST endpoints if you configure `**runtimeSyncSecret**`.

### Poll Endpoint Options

| **Param** | **Type** | **Default** | **Description** |
| --- | --- | --- | --- |
| `**limit**` | `**number**` | `**10**` | Max pages per batch (max: 50) |
| `**all**` | `**boolean**` | `**false**` | Process until complete |
| `**timeout**` | `**number**` | `**30000**` | Max ms for `**all**` mode |

### Prune Endpoint Options

| **Param** | **Type** | **Default** | **Description** |
| --- | --- | --- | --- |
| `**dry**` | `**boolean**` | `**false**` | Preview stale routes without deleting |
| `**ttl**` | `**number**` | `**pruneTtl**` config | Prune routes older than this (seconds) |
| `**secret**` | `**string**` | - | Auth token (required unless dry run) |

## Scheduled Indexing

When you set `**cron: true**`, Nitro scheduled tasks enable automatic background indexing (runs every minute):

nuxt.config.ts

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

The module auto-enables `**nitro.experimental.tasks**` when you configure cron.

**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**` with `**Authorization: Bearer <token>**` header. See the [**~~Cloudflare guide~~**](https://nuxtseo.com/docs/ai-ready/guides/cloudflare#cloudflare-pages) for details.

**IndexNow integration.** Combine runtime sync with [**~~IndexNow~~**](https://nuxtseo.com/docs/ai-ready/guides/indexnow) to notify search engines instantly when pages change. When you enable both `**runtimeSync.cron**` and `**indexNow**`, IndexNow syncs automatically after each scheduled indexing run.

## TTL Configuration

| **Option** | **Default** | **Description** |
| --- | --- | --- |
| `**ttl**` | `**3600**` | Re-index pages & refresh sitemap older than this (seconds) |
| `**pruneTtl**` | `**0**` | Delete routes not in sitemap for this long (0 = never) |

Force re-index regardless of TTL:

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

## Database Configuration

The module uses [**~~SQLite~~**](https://sqlite.org) via [**~~Drizzle ORM~~**](https://orm.drizzle.team/) for page storage. It auto-detects the best driver for your platform:

| **Platform** | **Driver** | **Extra dependency** |
| --- | --- | --- |
| [**~~Node.js~~**](https://nodejs.org) | `**better-sqlite3**` | `**better-sqlite3**` |
| [**~~Bun~~**](https://bun.sh) | `**bun:sqlite**` | None |
| [**~~Cloudflare~~**](https://cloudflare.com) | D1 | None |
| [**~~Vercel~~**](https://vercel.com) (with `**POSTGRES_URL**`) | Neon Postgres | None |

On Node.js you must install [`**better-sqlite3**`](https://github.com/WiseLibs/better-sqlite3) yourself, since it's an optional peer dependency:

```bash
pnpm add better-sqlite3
```

For edge deployments, configure D1 or LibSQL:

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

## Serverless Cold Starts

For serverless platforms (Vercel, [**~~Netlify~~**](https://netlify.com), 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

```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

```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

```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:

| **Function** | **Description** |
| --- | --- |
| `**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 |

## CLI

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

```bash
# 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~~**](https://nuxtseo.com/docs/ai-ready/guides/cli) for all commands and options.

**Was this page helpful?**

### **Related **

[**CLI**](https://nuxtseo.com/docs/ai-ready/guides/cli)

[**Configuration**](https://nuxtseo.com/docs/ai-ready/api/config)

[**Composables**](https://nuxtseo.com/docs/ai-ready/nitro-api/composables)

[**Nitro Hooks**](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks)

[**Model Context Protocol (MCP)** Connect AI agents like Claude to your Nuxt site via MCP servers with built-in tools and resources.](https://nuxtseo.com/docs/ai-ready/guides/mcp) [**Cloudflare Deployment** Deploy with Cloudflare D1 for persistent database storage.](https://nuxtseo.com/docs/ai-ready/guides/cloudflare)