---
title: "Composables"
description: "Server-side composables for accessing page data at runtime."
canonical_url: "https://nuxtseo.com/docs/ai-ready/nitro-api/composables"
last_updated: "2026-09-05T19:59:40.069Z"
---

These composables are auto-imported in Nitro server context (API routes, middleware, plugins).

## `queryPages`

**Type:** `(event?: H3Event, options?: QueryPagesOptions) => Promise<PageEntry[] | PageData[]>`{lang="ts"}

Unified page query function with filtering, pagination, and optional markdown content.

```ts [server/api/pages.get.ts]
import { queryPages } from '#ai-ready'

export default defineEventHandler(async (event) => {
  // Get all pages
  const pages = await queryPages(event)
  return pages
})
```

**Single page lookup:**

```ts [server/api/page.get.ts]
export default defineEventHandler(async (event) => {
  const { path } = getQuery(event)
  const page = await queryPages(event, { route: path as string, includeMarkdown: true })
  return page
})
```

**QueryPagesOptions:**

| Option            | Type                       | Description               |
| ----------------- | -------------------------- | ------------------------- |
| `route`           | `string`                   | Get single page by route  |
| `includeMarkdown` | `boolean`                  | Include markdown content  |
| `where.pending`   | `boolean`                  | Filter by indexing status |
| `where.hasError`  | `boolean`                  | Filter by error status    |
| `where.source`    | `'prerender' \| 'runtime'` | Filter by source          |
| `limit`           | `number`                   | Max pages to return       |
| `offset`          | `number`                   | Skip first N pages        |

**PageEntry:**

| Property      | Type       | Description                                             |
| ------------- | ---------- | ------------------------------------------------------- |
| `route`       | `string`   | Page route (e.g., `/about`)                             |
| `title`       | `string`   | Page title                                              |
| `description` | `string`   | Meta description                                        |
| `headings`    | `string`   | Pipe-separated headings (e.g., `h1:Title\|h2:Subtitle`) |
| `keywords`    | `string[]` | Extracted keywords                                      |
| `updatedAt`   | `string`   | ISO timestamp                                           |

**PageData:** Extends PageEntry with `markdown: string`.

## `searchPages`

**Type:** `(event: H3Event, query: string, options?: SearchPagesOptions) => Promise<SearchResult[]>`{lang="ts"}

Full-text search using SQLite FTS5. Searches title, description, route, headings, keywords, and markdown content.

```ts [server/api/search.get.ts]
import { searchPages } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const { q } = getQuery(event)
  return searchPages(event, q as string, { limit: 10 })
})
```

**SearchResult:**

| Property      | Type     | Description          |
| ------------- | -------- | -------------------- |
| `route`       | `string` | Page route           |
| `title`       | `string` | Page title           |
| `description` | `string` | Meta description     |
| `score`       | `number` | BM25 relevance score |

## `countPages`

**Type:** `(event?: H3Event, options?: CountPagesOptions) => Promise<number>`{lang="ts"}

Count pages matching criteria.

```ts [server/api/stats.get.ts]
import { countPages } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const total = await countPages(event)
  const pending = await countPages(event, { where: { pending: true } })
  return { total, pending, indexed: total - pending }
})
```

## `streamPages`

**Type:** `(event?: H3Event, options?: StreamPagesOptions) => AsyncGenerator<PageData>`{lang="ts"}

Stream pages using cursor-based pagination. Useful for large datasets.

```ts [server/api/export.get.ts]
import { streamPages } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const pages = []
  for await (const page of streamPages(event, { batchSize: 50 })) {
    pages.push({ route: page.route, title: page.title })
  }
  return pages
})
```

## `indexPage`

**Type:** `(route: string, html: string, options?: IndexPageOptions) => Promise<IndexPageResult>`{lang="ts"}

Manually index a page into the database.

```ts [server/api/reindex.post.ts]
import { indexPage } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const { path } = await readBody(event)
  const html = await $fetch(path)
  return indexPage(path, html, { force: true })
})
```

**Options:**

| Option     | Type      | Default      | Description                       |
| ---------- | --------- | ------------ | --------------------------------- |
| `ttl`      | `number`  | config value | Override TTL check                |
| `force`    | `boolean` | `false`      | Re-index even if fresh            |
| `skipHook` | `boolean` | `false`      | Skip `ai-ready:page:indexed` hook |

**Result:**

| Property         | Type      | Description                       |
| ---------------- | --------- | --------------------------------- |
| `success`        | `boolean` | Whether indexing succeeded        |
| `skipped`        | `boolean` | `true` if page was fresh          |
| `isUpdate`       | `boolean` | `true` if updating existing entry |
| `contentChanged` | `boolean` | `true` if content hash differs    |
| `data`           | `object`  | Page data if successful           |
| `error`          | `string`  | Error message if failed           |

## `indexPageByRoute`

**Type:** `(route: string, event?: H3Event, options?: IndexPageOptions) => Promise<IndexPageResult>`{lang="ts"}

Fetch HTML and index in one call.

```ts [server/plugins/warm-cache.ts]
import { indexPageByRoute } from '#ai-ready'

export default defineNitroPlugin(async () => {
  // Pre-warm important pages on startup
  const routes = ['/', '/docs', '/pricing']
  await Promise.all(routes.map(r => indexPageByRoute(r)))
})
```

## `useDatabase`

**Type:** `(event?: H3Event) => Promise<DatabaseAdapter>`{lang="ts"}

Direct database access for advanced queries.

```ts [server/api/custom.get.ts]
import { useDatabase } from '#ai-ready'

export default defineEventHandler(async (event) => {
  const db = await useDatabase(event)
  const rows = await db.all('SELECT route, title FROM ai_ready_pages WHERE indexed = 1')
  return rows
})
```

## Data Sources

| Context     | Source                                              |
| ----------- | --------------------------------------------------- |
| Development | Empty (warning logged)                              |
| Prerender   | [SQLite](https://sqlite.org) via virtual module     |
| Runtime     | SQLite via [Drizzle ORM](https://orm.drizzle.team/) |