> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/adelpro/quran-search-engine/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination options

> Control result pagination with page numbers and limits

The `search()` function includes built-in pagination support. Results are sliced server-side (or client-side) and returned with metadata about total results, pages, and current position.

## Overview

Pagination is controlled by the `PaginationOptions` parameter passed to `search()`:

```typescript theme={null}
import { search, type PaginationOptions } from 'quran-search-engine';

const response = search(
  'الله الرحمن',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 1, limit: 10 }
);
```

## PaginationOptions type

From `src/types/index.ts:62-65`:

```typescript theme={null}
export type PaginationOptions = {
  page?: number;
  limit?: number;
};
```

**Fields:**

* `page?: number` - Page number (1-indexed, defaults to 1)
* `limit?: number` - Results per page (defaults to 20)

<Note>
  Both fields are optional. If omitted, defaults are applied: `{ page: 1, limit: 20 }`
</Note>

## Default behavior

From `src/core/search.ts:309`:

```typescript theme={null}
export const search = <TVerse extends VerseInput>(
  query: string,
  quranData: TVerse[],
  morphologyMap: Map<number, MorphologyAya>,
  wordMap: WordMap,
  options: AdvancedSearchOptions = { lemma: true, root: true },
  pagination: PaginationOptions = { page: 1, limit: 20 },
): SearchResponse<TVerse> => {
```

When no pagination is provided:

* Returns first 20 results
* Current page is 1
* Total pages calculated based on total results

## Pagination metadata

The `SearchResponse` includes detailed pagination metadata:

```typescript theme={null}
const response = search(
  'الله الرحمن',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 1, limit: 10 },
);

// Example output:
// response.pagination => {
//   totalResults: 42,
//   totalPages: 5,
//   currentPage: 1,
//   limit: 10
// }
```

From `src/types/index.ts:67-76`:

```typescript theme={null}
export type SearchResponse<TVerse extends VerseInput = QuranText> = {
  results: ScoredVerse<TVerse>[];
  counts: SearchCounts;
  pagination: {
    totalResults: number;
    totalPages: number;
    currentPage: number;
    limit: number;
  };
};
```

## Implementation details

From `src/core/search.ts:366-374`:

```typescript theme={null}
// 6. Pagination & Metadata
const page = Math.max(1, pagination.page || 1);
const limit = Math.max(1, pagination.limit || 20);
const offset = (page - 1) * limit;

const results = combined.slice(offset, offset + limit);
const totalResults = combined.length;
const totalPages = Math.ceil(totalResults / limit);
```

<Steps>
  <Step title="Normalize page and limit">
    Both values are clamped to minimum of 1:

    ```typescript theme={null}
    const page = Math.max(1, pagination.page || 1);
    const limit = Math.max(1, pagination.limit || 20);
    ```
  </Step>

  <Step title="Calculate offset">
    Convert page number to array offset (0-indexed):

    ```typescript theme={null}
    const offset = (page - 1) * limit;
    ```
  </Step>

  <Step title="Slice results">
    Extract the requested page from all sorted results:

    ```typescript theme={null}
    const results = combined.slice(offset, offset + limit);
    ```
  </Step>

  <Step title="Calculate metadata">
    Determine total pages and include in response:

    ```typescript theme={null}
    const totalResults = combined.length;
    const totalPages = Math.ceil(totalResults / limit);
    ```
  </Step>
</Steps>

## Usage examples

### First page (default)

```typescript theme={null}
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap
);

console.log(response.pagination);
// {
//   totalResults: 2800,
//   totalPages: 140,
//   currentPage: 1,
//   limit: 20
// }
```

### Custom page size

```typescript theme={null}
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 1, limit: 50 }
);

console.log(response.pagination);
// {
//   totalResults: 2800,
//   totalPages: 56,
//   currentPage: 1,
//   limit: 50
// }
```

### Navigate to specific page

```typescript theme={null}
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 5, limit: 20 }
);

console.log(response.pagination);
// {
//   totalResults: 2800,
//   totalPages: 140,
//   currentPage: 5,
//   limit: 20
// }

// Results are from index 80 to 99 (zero-indexed)
```

### Get all results

```typescript theme={null}
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 1, limit: 10000 }
);

// All results on a single page
```

<Warning>
  Requesting very large page sizes (e.g., `limit: 10000`) may impact performance. Consider using reasonable page sizes (10-100) for better UX and performance.
</Warning>

## Building pagination UI

### React example

```tsx theme={null}
import { search, type SearchResponse } from 'quran-search-engine';
import { useState } from 'react';

export function SearchResults() {
  const [currentPage, setCurrentPage] = useState(1);
  const [response, setResponse] = useState<SearchResponse | null>(null);

  const performSearch = (query: string, page: number) => {
    const result = search(
      query,
      quranData,
      morphologyMap,
      wordMap,
      { lemma: true, root: true },
      { page, limit: 20 }
    );
    setResponse(result);
  };

  if (!response) return null;

  const { pagination } = response;

  return (
    <div>
      {/* Results */}
      <div>
        {response.results.map((verse) => (
          <div key={verse.gid}>{verse.uthmani}</div>
        ))}
      </div>

      {/* Pagination controls */}
      <div className="pagination">
        <button
          disabled={pagination.currentPage === 1}
          onClick={() => performSearch(query, currentPage - 1)}
        >
          Previous
        </button>

        <span>
          Page {pagination.currentPage} of {pagination.totalPages}
          ({pagination.totalResults} results)
        </span>

        <button
          disabled={pagination.currentPage === pagination.totalPages}
          onClick={() => performSearch(query, currentPage + 1)}
        >
          Next
        </button>
      </div>
    </div>
  );
}
```

### Page number buttons

```tsx theme={null}
function PageButtons({ pagination, onPageChange }: {
  pagination: SearchResponse['pagination'];
  onPageChange: (page: number) => void;
}) {
  const { currentPage, totalPages } = pagination;
  const maxButtons = 5;
  
  // Calculate range of page buttons to show
  let startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2));
  let endPage = Math.min(totalPages, startPage + maxButtons - 1);
  
  if (endPage - startPage + 1 < maxButtons) {
    startPage = Math.max(1, endPage - maxButtons + 1);
  }
  
  const pages = Array.from(
    { length: endPage - startPage + 1 },
    (_, i) => startPage + i
  );
  
  return (
    <div className="page-buttons">
      {startPage > 1 && (
        <>
          <button onClick={() => onPageChange(1)}>1</button>
          {startPage > 2 && <span>...</span>}
        </>
      )}
      
      {pages.map((page) => (
        <button
          key={page}
          className={page === currentPage ? 'active' : ''}
          onClick={() => onPageChange(page)}
        >
          {page}
        </button>
      ))}
      
      {endPage < totalPages && (
        <>
          {endPage < totalPages - 1 && <span>...</span>}
          <button onClick={() => onPageChange(totalPages)}>{totalPages}</button>
        </>
      )}
    </div>
  );
}
```

## Edge cases

### Invalid page numbers

```typescript theme={null}
// Page 0 or negative → clamped to 1
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 0, limit: 20 }
);
// response.pagination.currentPage => 1

// Page beyond total pages → returns empty results
const response2 = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 9999, limit: 20 }
);
// response2.results => []
// response2.pagination.currentPage => 9999
```

<Tip>
  Check if `currentPage > totalPages` to detect out-of-range requests and redirect to the last valid page.
</Tip>

### Zero or negative limit

```typescript theme={null}
// Limit 0 or negative → clamped to 1
const response = search(
  'الله',
  quranData,
  morphologyMap,
  wordMap,
  { lemma: true, root: true },
  { page: 1, limit: 0 }
);
// response.pagination.limit => 1
// response.results.length => 1
```

### No results

```typescript theme={null}
const response = search(
  'xyz123',
  quranData,
  morphologyMap,
  wordMap
);

// response.pagination => {
//   totalResults: 0,
//   totalPages: 0,
//   currentPage: 1,
//   limit: 20
// }
// response.results => []
```

## Performance considerations

<Note>
  Pagination happens **after** search and scoring. All results are computed first, then sliced. For very large result sets, consider implementing server-side pagination if performance becomes an issue.
</Note>

### Search flow

1. Search runs across all verses (6,236 verses)
2. Results are scored and sorted
3. **Then** pagination slices the sorted results
4. Only the requested page is returned

This means:

* ✓ Consistent scoring and ordering across pages
* ✓ Accurate total counts and page numbers
* ✗ All results must be computed even if only viewing page 1

### Optimization tips

```typescript theme={null}
// Cache the full result set and paginate client-side
let cachedResults: ScoredVerse[] = [];

function searchWithCache(query: string, page: number) {
  if (cachedResults.length === 0) {
    // First search: get ALL results
    const fullResponse = search(
      query,
      quranData,
      morphologyMap,
      wordMap,
      { lemma: true, root: true },
      { page: 1, limit: 10000 }
    );
    cachedResults = fullResponse.results;
  }
  
  // Paginate cached results
  const limit = 20;
  const offset = (page - 1) * limit;
  const results = cachedResults.slice(offset, offset + limit);
  
  return {
    results,
    pagination: {
      totalResults: cachedResults.length,
      totalPages: Math.ceil(cachedResults.length / limit),
      currentPage: page,
      limit,
    },
  };
}
```
