How Nitter Handles Cursor-Based Pagination in Its API Requests
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 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:
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:
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:
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– DefinescursorParam, builds GraphQL URLs for each timeline type (tweets, replies, media, etc.)src/routes/router_utils.nim– Retrieves and decodes thecursorquery parameter from HTTP requestssrc/routes/timeline.nim– Calls the appropriate GraphQL function with the cursor and passes the result to the viewsrc/views/timeline.nim– Renders the "more" link that carries the cursor forwardsrc/parser.nim– Parses GraphQL responses, extractsnext_cursor/cursor-bottomvaluessrc/parserutils.nim– Helper for reading cursor fields from JSON structures
Relevant lines in the codebase include:
src/api.nimlines 21-34 for cursor handlingsrc/routes/router_utils.nimlines 28-33 for cursor extractionsrc/routes/timeline.nimlines 165-169 for routing logicsrc/views/timeline.nimlines 53-55 for view renderingsrc/parser.nimlines 749-754 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.nimand decoded before processing. - The
cursorParamhelper inapi.nimsafely JSON-escapes cursors and injects them into GraphQL variables for each endpoint type. - Response parsers in
parser.nimlocatenext_cursor,cursor-bottom, orcursor-showmorefields to identify subsequent pages. - The view layer in
views/timeline.nimrenders "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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →