A few weeks ago, I noticed my Laravel + Inertia.js application displaying the homepage inside an Inertia error modal on a 200 response in production. At first, it felt like a client-side routing glitch or a misfiring Inertia event. After a closer look, the culprit wasn’t JavaScript—it was Cloudflare’s edge cache.
Inertia.js delivers both HTML and JSON responses from the same URL. My application had opted certain pages into Cloudflare’s edge caching with the s-maxage directive. Cloudflare, however, ignores the Vary: X-Inertia response header when evaluating cache hits. That meant Cloudflare could hand cached HTML to an XHR request that explicitly requested JSON via the X-Inertia: true header. The result? A page rendered as a modal error, even though the server returned a 200 status.
How the misconfiguration happened
Inertia.js serves two types of payloads for a single endpoint: full-page HTML for direct navigation and structured JSON for client-side transitions. When Inertia pages are cached at Cloudflare’s edge, the system needs to distinguish between these responses. The Vary: X-Inertia header signals to caches that responses should vary based on the X-Inertia request header, which is either true or omitted. Without this, Cloudflare treats all responses as identical, regardless of the incoming header.
In my case, the HTML payload was stored with a s-maxage=3600 directive. When a subsequent XHR request arrived with X-Inertia: true, Cloudflare served the cached HTML instead of the expected JSON. A quick curl inspection confirmed the issue:
curl -I -H "X-Inertia: true" The response included:
cf-cache-status: HITEven though the XHR explicitly requested JSON, Cloudflare returned the HTML variant because it had no mechanism to differentiate payloads at cache lookup time.
Why prior defenses failed
I had already set Cache-Control: no-store on JSON responses. At first glance, this should have prevented caching entirely. However, the problem occurs at cache lookup, not cache storage. When Cloudflare evaluates a request, it first checks its edge cache. If it finds a matching entry—regardless of the response’s no-store—it returns the cached version before considering the headers on the new request. Only then would the server’s no-store header be evaluated, but by then the damage was done.
The distinction between storage-time and lookup-time defenses is critical. Headers like no-store protect fresh requests from being affected by stale caches, but they don’t prevent Cloudflare from serving a cached response if it already exists and matches the URL exactly.
The one-line fix (and a safer rule)
The simplest solution is to add Vary: X-Inertia to all Inertia responses. This ensures Cloudflare caches HTML and JSON variants separately. After deploying the change, the curl test should return cf-cache-status: MISS for XHR requests with X-Inertia: true, indicating Cloudflare didn’t reuse the HTML variant.
For teams that want finer control, Cloudflare Cache Rules let you conditionally cache Inertia responses based on the X-Inertia header. A rule like this prevents HTML caching for XHR requests:
{
"id": "inertia-json-rule",
"enabled": true,
"description": "Do not cache JSON responses sent to Inertia XHRs",
"expression": "!(http.request.headers.x_inertia == "true")",
"action": "set_cache_settings",
"cache_settings": {
"edge_ttl": 86400
}
}This approach keeps HTML caching enabled for direct page loads while ensuring XHR requests receive fresh JSON payloads.
How to check if your Inertia app is affected
A 30-second test can reveal whether your Inertia.js app is vulnerable to this cache trap. Run the following curl commands and inspect the cf-cache-status header:
# Test HTML variant (direct navigation)
curl -I
# Test JSON variant (XHR request)
curl -I -H "X-Inertia: true" If the second request returns cf-cache-status: HIT, your Inertia app is serving cached HTML to XHR endpoints. Applying Vary: X-Inertia or a Cache Rule will resolve the issue.
Preventing future cache collisions
Cloudflare’s edge cache is powerful, but it demands careful configuration when used with dynamic frameworks like Inertia.js. Teams should audit their caching policies, especially when the same URL serves multiple content types. Adding Vary headers and conditional Cache Rules can prevent subtle bugs that only surface under specific request patterns.
The fix is straightforward, but the lesson lingers: edge caching and client-side frameworks don’t always play well together out of the box. A few lines of configuration can save hours of debugging—and keep error modals where they belong: in development.
AI summary
Laravel projelerinizde Inertia.js kullanırken Cloudflare önbellek hatasından kaçınmanın yollarını keşfedin. Basit adımlarla JSON ve HTML yanıtlarını ayırın.