# How Nitter Handles Cursor-Based Pagination in Its API Requests

> Discover how Nitter manages cursor-based pagination in API requests. Learn how it extracts cursor tokens, injects them into GraphQL queries, and updates 'Show more' links for a seamless user experience.

- Repository: [Zed/nitter](https://github.com/zedeus/nitter)
- Tags: api-reference
- Published: 2026-09-04

---

**Nitter implements cursor-based pagination by extracting cursor tokens from HTTP query parameters, injecting them into Twitter GraphQL requests via the `cursorParam` helper in `src/api.nim`, and propagating the returned `next_cursor` from JSON responses into HTML "Show more" links rendered by `src/views/timeline.nim`.**

The open-source Twitter alternative front-end [zedeus/nitter](https://github.com/zedeus/nitter) relies on cursor-based pagination to fetch timeline data while maintaining stateless HTTP interactions. This mechanism allows Nitter to request successive slices of tweets, replies, and media without exposing Twitter's internal API limits. The implementation spans multiple Nim modules that handle parameter extraction, GraphQL variable construction, and response parsing.

## The Cursor Pagination Pipeline

### Extracting Cursor Parameters from Requests

In `src/routes/router_utils.nim`, Nitter reads incoming cursor values from the `cursor` query parameter or falls back to the legacy `max_position` parameter. The code retrieves and URL-decodes the string before passing it downstream to timeline route handlers.

According to the source code, the extraction logic appears as:

```nim
let cursor = req.params.getOrDefault("cursor")
let decoded = decodeUrl(if cursor.len > 0: cursor else: @"max_position", false)

```

### Encoding Cursors for GraphQL Variables

The `cursorParam` procedure in `src/api.nim` safely JSON-escapes the cursor value and injects it into GraphQL variables. This helper ensures the cursor string is properly formatted as `"cursor": "<encoded-value>"` within the request payload sent to Twitter's API endpoints.

### Endpoint-Specific URL Construction

Depending on the timeline type—tweets, replies, media, or articles—`src/api.nim` selects the appropriate GraphQL endpoint. Functions including `userTweetsUrl`, `userTweetsAndRepliesUrl`, `mediaUrl`, and `userArticlesUrl` all accept the cursor string as a parameter and embed it into the API request structure.

For example, `userTweetsUrl` builds the request as follows:

```nim
proc userTweetsUrl(id: string; cursor: string): ApiReq =
  # `cursorParam` injects `"cursor": "<value>"` into the variables

  return apiReq(graphUserTweetsV2, restIdVars % [id, cursor, "20"], userTweetsFieldToggles)

```

### Parsing Next Cursors from Responses

After fetching data, `src/parser.nim` locates continuation tokens within the GraphQL JSON response. The parser searches for fields named `cursor-bottom`, `cursor-showmore`, or `next_cursor`, storing these values in result objects such as `result.bottom` or `result.thread.cursor` for subsequent use.

### Rendering Pagination Controls

In `src/views/timeline.nim`, the `renderMore` procedure constructs the "Show more" hyperlink. This function URL-encodes the cursor and appends it to the request URL as a `cursor=` query parameter, completing the cycle when users click to load the next page.

The view implementation appears as:

```nim
proc renderMore*(query: Query; cursor: string; focus = ""; extra = ""): VNode =
  a(href = &"?{extra}{getQuery(query)}cursor={encodeUrl(cursor, usePlus = false)}{focus}") :
    # link text …

```

## Key Source Files

The cursor-based pagination system spans the following files in the repository:

- **`src/api.nim`** – Defines `cursorParam`, builds GraphQL URLs for each timeline type (tweets, replies, media, etc.)
- **`src/routes/router_utils.nim`** – Retrieves and decodes the `cursor` query parameter from HTTP requests
- **`src/routes/timeline.nim`** – Calls the appropriate GraphQL function with the cursor and passes the result to the view
- **`src/views/timeline.nim`** – Renders the "more" link that carries the cursor forward
- **`src/parser.nim`** – Parses GraphQL responses, extracts `next_cursor` / `cursor-bottom` values
- **`src/parserutils.nim`** – Helper for reading cursor fields from JSON structures

Relevant lines in the codebase include:
- [`src/api.nim` lines 21-34](https://github.com/zedeus/nitter/blob/master/src/api.nim#L21-L34) for cursor handling
- [`src/routes/router_utils.nim` lines 28-33](https://github.com/zedeus/nitter/blob/master/src/routes/router_utils.nim#L28-L33) for cursor extraction
- [`src/routes/timeline.nim` lines 165-169](https://github.com/zedeus/nitter/blob/master/src/routes/timeline.nim#L165-L169) for routing logic
- [`src/views/timeline.nim` lines 53-55](https://github.com/zedeus/nitter/blob/master/src/views/timeline.nim#L53-L55) for view rendering
- [`src/parser.nim` lines 749-754](https://github.com/zedeus/nitter/blob/master/src/parser.nim#L749-L754) for cursor parsing

## Summary

- **Nitter** uses Twitter's GraphQL cursor mechanism to page through timeline data including tweets, replies, and media.
- The **cursor parameter** is extracted from HTTP requests in `router_utils.nim` and decoded before processing.
- The **`cursorParam`** helper in `api.nim` safely JSON-escapes cursors and injects them into GraphQL variables for each endpoint type.
- **Response parsers** in `parser.nim` locate `next_cursor`, `cursor-bottom`, or `cursor-showmore` fields to identify subsequent pages.
- The **view layer** in `views/timeline.nim` renders "Show more" links that encode the cursor into the URL, enabling seamless pagination without exposing Twitter's raw API.

## Frequently Asked Questions

### How does Nitter handle the initial page request when no cursor exists?

When no cursor parameter is present in the URL, Nitter treats the request as an initial page load. The `router_utils.nim` logic uses an empty string or default value, which the GraphQL functions in `api.nim` handle by omitting the cursor from the variables, causing Twitter's API to return the first page of results.

### What is the difference between `cursor` and `max_position` parameters?

The `cursor` parameter is the modern query string key used for pagination, while `max_position` serves as a legacy fallback parameter supported for backward compatibility. The extraction logic in `src/routes/router_utils.nim` checks for `cursor` first, then falls back to `max_position` if the former is absent.

### How does Nitter determine when to show the "Show more" button?

The parser in `src/parser.nim` extracts continuation tokens like `cursor-bottom` or `next_cursor` from the GraphQL response. If these fields contain values, the view layer passes them to `renderMore` in `src/views/timeline.nim`, which generates the navigation link. Absence of these tokens indicates the final page.

### Is cursor encoding necessary for the GraphQL requests?

Yes, cursor encoding is essential because Twitter's GraphQL variables require properly escaped JSON strings. The `cursorParam` function in `src/api.nim` ensures special characters within cursor tokens do not break the JSON structure when injected into requests via functions like `userTweetsUrl` or `mediaUrl`.