Independent Editorial DeskWordPress Releases, Builds, and Operations
Back to Archive
Implementation Notes

WordPress REST API Pagination Example: Fetch Posts for a Headless Frontend

A practical headless WordPress REST API pagination guide covering per_page, page, X-WP-TotalPages, cache revalidation, sitemap rules, and archive SEO.

Published

April 19, 2026

Reading Time

5 min read

Updated

April 19, 2026

Abstract paginated content cards and arrows representing WordPress REST API archive pagination.
Build PatternImplementation Notes

Implementation Notes

Extension points, code paths, and implementation choices that should survive contact with production.

Best For

WordPress developers, agencies, and technical teams building custom plugin or theme functionality with cleaner operational defaults.

Primary Topics

Implementation Notes

Editorial Focus

Build Pattern: Extension points, code paths, and implementation choices that should survive contact with production. Updated on April 19, 2026.

Full Report

Last reviewed: April 19, 2026

Headless WordPress projects usually fail at the same place: the first page of posts works, but pagination, cache behavior, total page counts, and error handling are treated as afterthoughts. That creates broken archives, slow builds, missing articles, and confusing search engine discovery.

This guide shows a practical WordPress REST API pagination example for a headless frontend. The examples use JavaScript-style fetch code, but the same rules apply whether the frontend is built with Next.js, Astro, Remix, Nuxt, SvelteKit, or a static build pipeline.

Who this guide is for

This article is for developers building a frontend that consumes WordPress posts from /wp-json/wp/v2/posts. It is also useful for SEO teams and site owners who want to understand why pagination must be handled intentionally instead of simply fetching the latest 10 posts.

How WordPress REST pagination works

WordPress collection endpoints support page and per_page. The important detail is that total counts are returned in response headers, not in the JSON body. The two headers to read are X-WP-Total and X-WP-TotalPages.

curl -i 'https://example.com/wp-json/wp/v2/posts?per_page=10&page=1'

A production frontend should use those headers to decide whether older pages exist. Do not guess based on whether the first request returns exactly 10 posts. Drafts, private posts, date filters, category filters, and future changes can all make guessing unreliable.

Use a narrow REST response

The default post response contains more data than most archive pages need. Use _fields to request only the fields used by the frontend. This reduces payload size and makes caching more predictable.

/wp-json/wp/v2/posts?per_page=10&page=1&_fields=id,slug,date,modified,title,excerpt,featured_media

If the archive needs featured image details, use _embed carefully. It is convenient, but it increases response size. For high-traffic sites, compare embedded media against a second targeted media request or a custom endpoint that returns exactly the data the frontend needs.

Fetch one archive page

async function fetchPostPage( page = 1, perPage = 10 ) {
	const params = new URLSearchParams( {
		page: String( page ),
		per_page: String( perPage ),
		_fields: 'id,slug,date,modified,title,excerpt,featured_media',
	} );

	const response = await fetch(
		`https://example.com/wp-json/wp/v2/posts?${ params.toString() }`,
		{
			headers: {
				Accept: 'application/json',
			},
		}
	);

	if ( ! response.ok ) {
		throw new Error( `WordPress posts request failed: ${ response.status }` );
	}

	return {
		posts: await response.json(),
		total: Number( response.headers.get( 'X-WP-Total' ) || 0 ),
		totalPages: Number( response.headers.get( 'X-WP-TotalPages' ) || 0 ),
	};
}

This wrapper returns posts and pagination metadata together. That keeps rendering code simple and prevents the common mistake of losing header data after converting the response to JSON.

Generate all archive pages

For static generation or sitemap generation, request page one first, read X-WP-TotalPages, then loop through the remaining pages.

async function fetchAllPostPages() {
	const firstPage = await fetchPostPage( 1, 10 );
	const pages = [ firstPage ];

	for ( let page = 2; page <= firstPage.totalPages; page++ ) {
		pages.push( await fetchPostPage( page, 10 ) );
	}

	return pages;
}

Keep per_page reasonable. WordPress caps it at 100, but a headless blog archive normally should not need that much content in a single request. Ten to twenty posts per archive page is easier to cache and usually better for UX.

Handle empty and out-of-range pages

If a visitor requests a page that no longer exists, return a proper 404 instead of showing an empty archive. This can happen after posts are deleted, unpublished, or filtered differently.

async function getArchivePageOrNotFound( page ) {
	const data = await fetchPostPage( page, 10 );

	if ( page > 1 && data.posts.length === 0 ) {
		return {
			notFound: true,
		};
	}

	return data;
}

That rule helps search engines understand which archive URLs are valid. It also prevents thin empty pages from being indexed accidentally.

SEO rules for headless pagination

  • Give every archive page a unique canonical URL.
  • Do not canonicalize page 2 and older pages back to page 1.
  • Include paginated archive URLs in internal links, not only JavaScript state.
  • Keep article links crawlable in the initial HTML whenever possible.
  • Return 404 for invalid page numbers instead of returning a blank page.

Performance checklist

  • Use _fields to keep payloads small.
  • Avoid offset for normal page navigation when page works.
  • Cache archive requests, but revalidate after publishing new content.
  • Do not fetch every post on every request just to slice in the frontend.
  • Log REST errors so build failures are visible before deployment.

Revalidation strategy

A headless frontend needs a publishing strategy. If the frontend statically generates blog archives, new posts will not appear until the cache is revalidated or the site is rebuilt. If the frontend renders pages on request, the WordPress API must be reachable and fast enough for that path.

A practical setup is to cache archive pages for a short interval and trigger revalidation after publishing. The exact mechanism depends on the frontend framework, but the rule is the same: publishing in WordPress should have a predictable path to visible frontend updates.

// Pseudocode for a publish webhook target.
export async function revalidateBlogAfterPublish( payload ) {
	if ( payload.status !== 'publish' ) {
		return;
	}

	await revalidatePath( '/' );
	await revalidatePath( '/blog' );
	await revalidatePath( `/blog/${ payload.slug }` );
}

Do not rely only on long time-based cache expiration for fresh content. Search engines and returning readers should see new articles quickly after publish.

Sitemap considerations

The sitemap should include article URLs and important archive pages. It does not need every possible filter combination. If the frontend creates paginated archive URLs, include only valid pages and make sure lastmod values are valid ISO dates. Invalid dates can cause Search Console warnings even when the URLs are otherwise reachable.

Common mistakes

  • Ignoring headers. The body does not contain total page count.
  • Fetching too much data. Full REST responses are unnecessary for most listing pages.
  • Using client-only pagination. Search engines need crawlable links and server-rendered archive pages.
  • Not handling deleted posts. Archive page counts can change after content updates.
  • Caching forever. Freshly published posts should reach the frontend predictably.

Related reading

This pairs naturally with our headless WordPress security checklist and the REST API exposure checklist. Pagination is not only a frontend concern; it is part of the content delivery contract.

References and further reading

Popular Guides

Popular WordPress guides to read next.

These articles connect recurring production concerns: implementation details, updates, troubleshooting, recovery paths, and operational cleanup.

Continue Reading

More from the archive.

Diagnostic dashboard scene representing a WordPress Site Health review before major updates.
01Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Site Health Check Before Major Updates: What to Review First

A pre-update WordPress Site Health checklist covering loopbacks, connectivity, debug settings, and environment readiness.

Structured data and route review scene representing permalink validation after a WordPress migration.
02Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Permalink Checklist After Migration: Catch URL Problems Early

A post-migration WordPress permalink checklist for checking rewrite rules, post URLs, archives, and redirect noise.

Technical media workspace representing image preparation and optimization before upload to WordPress.
03Build Pattern
Implementation Notes

Build Pattern

Extension points, code paths, and implementation choices that should survive contact with production.

May 21, 2026 · 3 min read

WordPress Image Optimization Checklist: What to Fix Before Upload

A practical WordPress image optimization checklist covering dimensions, compression, formats, and Media settings before upload.