---
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-05-06T21:34:27.551Z"
---

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

## `queryPages`

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

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

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        route
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Get single page by route
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        includeMarkdown
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Include markdown content
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        where.pending
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Filter by indexing status
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        where.hasError
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Filter by error status
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        where.source
      </code>
    </td>
    
    <td>
      <code>
        'prerender' | 'runtime'
      </code>
    </td>
    
    <td>
      Filter by source
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        limit
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      Max pages to return
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        offset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      Skip first N pages
    </td>
  </tr>
</tbody>
</table>

**PageEntry:**

<table>
<thead>
  <tr>
    <th>
      Property
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        route
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Page route (e.g., <code>
        /about
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Page title
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        description
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Meta description
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        headings
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Pipe-separated headings (e.g., <code>
        h1:Title|h2:Subtitle
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        keywords
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      Extracted keywords
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        updatedAt
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      ISO timestamp
    </td>
  </tr>
</tbody>
</table>

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

## `searchPages`

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

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

<table>
<thead>
  <tr>
    <th>
      Property
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        route
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Page route
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Page title
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        description
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Meta description
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        score
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      BM25 relevance score
    </td>
  </tr>
</tbody>
</table>

## `countPages`

**Type:** `(event?: H3Event, options?: CountPagesOptions) => Promise<number>`

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

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

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

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ttl
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      config value
    </td>
    
    <td>
      Override TTL check
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        force
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Re-index even if fresh
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        skipHook
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Skip <code>
        ai-ready:page:indexed
      </code>
      
       hook
    </td>
  </tr>
</tbody>
</table>

**Result:**

<table>
<thead>
  <tr>
    <th>
      Property
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        success
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Whether indexing succeeded
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        skipped
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       if page was fresh
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isUpdate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       if updating existing entry
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        contentChanged
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       if content hash differs
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        data
      </code>
    </td>
    
    <td>
      <code>
        object
      </code>
    </td>
    
    <td>
      Page data if successful
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        error
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Error message if failed
    </td>
  </tr>
</tbody>
</table>

## `indexPageByRoute`

**Type:** `(route: string, event?: H3Event, options?: IndexPageOptions) => Promise<IndexPageResult>`

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

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

<table>
<thead>
  <tr>
    <th>
      Context
    </th>
    
    <th>
      Source
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Development
    </td>
    
    <td>
      Empty (warning logged)
    </td>
  </tr>
  
  <tr>
    <td>
      Prerender
    </td>
    
    <td>
      <a href="https://sqlite.org" rel="nofollow">
        SQLite
      </a>
      
       via virtual module
    </td>
  </tr>
  
  <tr>
    <td>
      Runtime
    </td>
    
    <td>
      SQLite via db0
    </td>
  </tr>
</tbody>
</table>
