# Paginate every page

URL: https://developers.uidu.org/docs/recipes/paginate

> Walk every page of a Relay connection with the paginate() helper.

Most `list*` functions in `@uidu/client` return the first page only — they're tuned for the common case of "show the first N." When you need *everything* (a sitemap, `generateStaticParams`, an export), use the `paginate()` helper.

## What it does

`paginate()` repeatedly calls a list query, threading `pageInfo.endCursor` into the next call's `after` argument until `hasNextPage` is `false`. Returns a flat array of nodes.

## Signature

```ts
import { paginate, ListEventsDocument } from '@uidu/client';

const all = await paginate(client, ListEventsDocument, {
  variables: { first: 100 },
  extract: (d) => d.currentWorkspace?.events,
});
```

| Argument    | Type                                | Notes                                                      |
| ----------- | ----------------------------------- | ---------------------------------------------------------- |
| `client`    | `UiduClient`                        | Created with `createClient`.                               |
| `document`  | `TypedDocumentNode`                 | Any `List*Document` re-exported from `@uidu/client`.       |
| `variables` | `Omit<Vars, 'after'>`               | First-page variables. `after` is set by `paginate` itself. |
| `extract`   | `(data) => RelayConnection \| null` | Pluck the connection out of the result shape.              |
| `maxPages`  | `number` (default 1000)             | Safety bound — guards against runaway loops.               |

## Sitemap with every page

```tsx
// app/sitemap.ts
import { paginate, ListPagesDocument } from '@uidu/client';
import { client } from '@/lib/uidu';

export default async function sitemap() {
  const pages = await paginate(client, ListPagesDocument, {
    variables: { projectId: process.env.UIDU_PROJECT_ID!, first: 100 },
    extract: (d) => d.currentWorkspace?.project?.pages,
  });

  return pages.map((p) => ({
    url: `https://example.com/${p.slug}`,
    lastModified: p.updatedAt,
  }));
}
```

## generateStaticParams for every event

```tsx
// app/events/[id]/page.tsx
import { paginate, ListEventsDocument } from '@uidu/client';
import { client } from '@/lib/uidu';

export async function generateStaticParams() {
  const events = await paginate(client, ListEventsDocument, {
    variables: { first: 100 },
    extract: (d) => d.currentWorkspace?.events,
  });
  return events.map((e) => ({ id: e.id }));
}
```

## Other List Documents

The same pattern works with any of the re-exported Documents:

`ListBookingsDocument`, `ListCalendarsDocument`, `ListCalendarEventsDocument`, `ListCallsDocument`, `ListCampaignsDocument`, `ListEmailCampaignsDocument`, `ListCoursesDocument`, `ListDealsDocument`, `ListDonationCampaignsDocument`, `ListEmployeesDocument`, `ListEnrollmentsDocument`, `ListEventsDocument`, `ListFormsDocument`, `ListKbArticlesDocument`, `ListKbCollectionsDocument`, `ListLecturesDocument`, `ListPagesDocument`, `ListStoriesDocument`.

## Why a Document, not the list function?

The user-facing `listEvents(client, vars)` returns a flat array — it throws away `pageInfo` to keep the common case ergonomic. `paginate()` needs `pageInfo`, so it works one level lower against the raw `TypedDocumentNode`.

<Callout type="warn">
  Be mindful with very large collections. `paginate()` is memory-bound — it holds the full result in RAM. For exports of 10k+ records, prefer streaming the data straight to disk or a downstream sink instead.
</Callout>
