Pagination rel prev and next tag checker
The pagination rel tags checker validates whether an ordered series of pages forms one consistent chain.
Run — free
Supply each page URL together with the destination found in its rel=prev and rel=next tags. The result identifies missing links, unexpected endpoint links, self-links, duplicate URLs, and destinations that skip or point to the wrong neighbor. It performs an exact, deterministic comparison without requesting any page, making it suitable for build checks, crawl exports, and repeatable technical SEO audits.
Describe the series in its intended order
Start with the page that search engines and visitors should treat as the beginning of the series, then add every later page in sequence. For each record, enter the absolute page URL and the destinations declared by its rel=prev and rel=next tags. The checker treats the array order as your claimed canonical order. That makes the expected relationship unambiguous: page two must name page one as its previous page, page one must name page two as its next page, and the same rule continues through the series. The first record should omit prev because nothing precedes it, while the last should omit next because nothing follows it. Use the actual tag destinations, not the URLs you hoped the templates would emit. Exact input lets the report expose subtle disagreements such as a trailing slash change, a different query parameter, or a link that skips directly from page two to page four. A list with fewer than two records is rejected because it cannot demonstrate a pagination relationship.
Understand each reported issue
A valid result means every adjacent pair agrees in both directions: the earlier page points forward to the later page, and the later page points back to the earlier one. A missing_link issue means a relationship required by the supplied order was absent. An unexpected_link issue appears when the first page declares prev or the last page declares next, extending the chain beyond the submitted series. A mismatched_link points somewhere other than the immediate expected neighbor, which can reveal skipped pages, stale template values, or records placed in the wrong order. A self_link means a page points to itself instead of another member of the series. Duplicate URLs receive their own issue because repeating a page makes the claimed order ambiguous even if nearby tag strings appear to match. Each issue includes the one-based page position, page URL, affected field, expected destination, and actual destination. The checker does not fetch URLs or decide whether a destination responds; it evaluates only the relationship data you provide.
Use the result in an SEO quality workflow
Run this check after extracting link elements from rendered pages, a crawler export, or a template test. In continuous integration, convert the result into a release condition by requiring valid to be true before a pagination template is deployed. During an audit, keep the issues beside the crawl snapshot so developers can reproduce exactly which relationship failed without crawling the site again. Comparison is intentionally exact after surrounding whitespace is removed. The checker does not normalize query strings, remove fragments, merge HTTP with HTTPS, or guess whether trailing slashes are equivalent, because those transformations could hide a real canonicalization problem. Correct the source tags or correct the submitted order, then run the same records again until the issue count reaches zero. The browser tool is convenient for a manual spot check, while automated requests cost $0.002 each and return the same deterministic structure. For full coverage, pair this relationship check with separate validation of status codes, canonical tags, indexability, and rendered HTML extraction, since those concerns require page access and are outside this capability.
What you can do with it
Test a pagination template before release
Feed generated tag destinations into a build check and stop deployment when adjacent pages do not point to each other.
Review a crawler export
Convert extracted prev and next destinations into ordered records and locate skipped, stale, or unexpected links.
Verify a migration
Check that rewritten pagination URLs still form the intended chain after routes, query parameters, or trailing-slash rules change.
FAQ
Does the checker visit the supplied URLs?
No. It performs no network requests and validates only the ordered URLs and rel tag destinations you supply.
What happens if I provide only one page?
The request is rejected as invalid input because at least two pages are required to validate a pagination relationship.
Should the first page include rel=prev?
No. The first page must omit prev, and any non-empty previous destination on that record is reported as unexpected.
Are equivalent-looking URLs normalized?
No. Apart from trimming surrounding whitespace, URLs are compared exactly so canonicalization differences are not hidden.
What does a valid result prove?
It proves that the supplied tags match the supplied order in both directions. It does not prove that pages respond, render those tags, or are indexable.
How much does an automated check cost?
Each API request costs $0.002. The browser version runs locally for interactive checks.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/seo/pagination-tags-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"pages":[{"url":"https://example.com/articles?page=1","next":"https://example.com/articles?page=2"},{"url":"https://example.com/articles?page=2","prev":"https://example.com/articles?page=1","next":"https://example.com/articles?page=3"},{"url":"https://example.com/articles?page=3","prev":"https://example.com/articles?page=2"}]}'const res = await fetch("https://api.kit.forhosting.com/seo/pagination-tags-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"pages": [
{
"url": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=2"
},
{
"url": "https://example.com/articles?page=2",
"prev": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=3"
},
{
"url": "https://example.com/articles?page=3",
"prev": "https://example.com/articles?page=2"
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/seo/pagination-tags-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"pages": [
{
"url": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=2"
},
{
"url": "https://example.com/articles?page=2",
"prev": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=3"
},
{
"url": "https://example.com/articles?page=3",
"prev": "https://example.com/articles?page=2"
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/seo/pagination-tags-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"pages":[{"url":"https://example.com/articles?page=1","next":"https://example.com/articles?page=2"},{"url":"https://example.com/articles?page=2","prev":"https://example.com/articles?page=1","next":"https://example.com/articles?page=3"},{"url":"https://example.com/articles?page=3","prev":"https://example.com/articles?page=2"}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"pages":[{"url":"https://example.com/articles?page=1","next":"https://example.com/articles?page=2"},{"url":"https://example.com/articles?page=2","prev":"https://example.com/articles?page=1","next":"https://example.com/articles?page=3"},{"url":"https://example.com/articles?page=3","prev":"https://example.com/articles?page=2"}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/seo/pagination-tags-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"pages": [
{
"url": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=2"
},
{
"url": "https://example.com/articles?page=2",
"prev": "https://example.com/articles?page=1",
"next": "https://example.com/articles?page=3"
},
{
"url": "https://example.com/articles?page=3",
"prev": "https://example.com/articles?page=2"
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "seo.pagination_tags_check",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |