Learn how to implement headless WordPress using Next.js ISR and WPGraphQL. Optimize frontend performance and solve cache invalidation issues in production.

During our Q3 migration of a high-traffic media site, we hit a wall with content updates. Our editors would hit "Update" in WordPress, but the Next.js frontend kept serving stale post data for roughly 14 minutes, despite our revalidate setting being set to 60 seconds. Digging into the logs, we found the issue wasn't the Next.js cache itself, but a race condition between the WPGraphQL query execution and the WP-Engine object cache clearing.
When you move to headless WordPress, you lose the instant cache clearing that standard PHP-based themes provide. You're effectively building a distributed system where the CMS and the frontend are decoupled, meaning you have to be intentional about data consistency.
We rely on Next.js ISR (Incremental Static Regeneration) to balance build times with content freshness. Instead of rebuilding the entire site on every post update—which would take hours for a site with 50,000+ nodes—we generate pages on demand and update them in the background.
The architecture looks like this:
post_updated./api/revalidate.res.revalidate('/post-slug').However, the "14-minute" delay was caused by a stale response cached by the WP-Engine edge layer. Even when Next.js requested the fresh data from WPGraphQL, the CMS was returning the cached version from the persistent object cache. We had to implement a specific purge command in our webhook logic to clear the WP-Engine cache before Next.js attempted to fetch the fresh data.
We first tried to force a full site rebuild using a static export on every save. That failed because the build process spiked our CI/CD runner memory usage by 400%, eventually crashing the deployment pipeline. We then moved to a granular revalidation strategy.
The trade-off here is that you can't guarantee 100% real-time updates without a more complex event-driven architecture. We accepted a "near-real-time" model where the user sees the update within 2–3 seconds of the API call. If you're struggling with similar infrastructure bottlenecks, you might find that WordPress Kubernetes Performance: Scaling with HPA and Redis helps you understand how to manage the resource overhead of these background tasks.
Using WPGraphQL effectively is the difference between a snappy site and one that triggers 504 errors. We keep our queries flat and avoid deep nesting, which is a common performance killer.
GraphQLquery GetPost($id: ID!) { post(id: $id, idType: SLUG) { title content date author { node { name } } } }
By fetching only what's needed for the specific page template, we reduced our average payload size by roughly 42KB. It’s worth noting that if you're dealing with complex component trees, you should look into React Performance Patterns You Actually Need in 2025 to ensure your frontend isn't re-rendering unnecessarily when that data finally arrives.

Frontend performance is the primary reason to go headless, but it's easy to ship a "heavy" site if you aren't careful. Since we're using Next.js, we leverage next/image to handle responsive assets, offloading the heavy lifting from the WordPress media library.
We also monitor our external dependencies closely. If your API calls to WordPress are slow, it ripples through your entire stack. I've found that tracking these latencies is best handled by custom observability tools, similar to how we Implement Laravel Pulse for Real-Time Infrastructure Monitoring to catch bottlenecks before they impact the user experience.
Why is my Next.js ISR not updating immediately?
Usually, it’s a cache header conflict. Check if your WP-Engine or CDN is caching the JSON response from WPGraphQL. You need to ensure Cache-Control headers are set to no-cache for the GraphQL endpoint.
Is WPGraphQL slow for large datasets? It can be, especially if you use complex field resolvers. Use persisted queries to reduce the parsing overhead on the WordPress server.
Should I use ISR or On-Demand Revalidation? Use both. ISR handles the initial generation, and On-Demand Revalidation (via webhooks) handles the updates.
I'm still not entirely convinced that our current webhook-based invalidation is the most robust approach. It feels fragile; if the webhook fails, the content stays stale indefinitely. We’ve discussed moving toward a message queue (like RabbitMQ) to guarantee the revalidation event is processed, but that’s a significant jump in complexity. For now, we're monitoring the failure rate and manually triggering purges when things go sideways.
Boost WordPress performance with Redis object caching. Learn to configure WP-Redis and W3 Total Cache to slash database queries and scale your site effectively.