CDN troubleshooting
Fix caching, static file, compression, header, and routing problems on Easel's CDN.
Start with the response headers, and test with curl rather than a browser so
the browser's own cache doesn't hide the result:
X-Easel-Cache tells you what the CDN did, and X-Easel-Id finds the request
in Requests. Easel removes
CDN-Cache-Control before the response reaches you, so check your code for the
policy it sent. See Caching.
| You see | What happened |
|---|---|
HIT or STALE | Served from the CDN cache; no function ran |
MISS | Not in cache; the deployment or your function produced it |
BYPASS | Not eligible for caching |
3xx with Location | A redirect matched before any route ran |
| No function log | Cached, static, redirected, or blocked before your code |
Other problems have their own pages:
| Problem | Page |
|---|---|
| Certificate warnings, HTTPS not active, DNS | Domain troubleshooting |
| Blocked or challenged requests | Security troubleshooting |
| Builds, missing environment variables, works locally but not deployed | Deployment troubleshooting |
| Slow responses, tracing a failing request | Troubleshooting |
Responses aren't cached
X-Easel-Cache is BYPASS
The request or response failed an eligibility check. Look for, in order of how often they cause it:
- A
Set-Cookieheader on the response. private,no-store, orno-cache, or no positive lifetime.- An
AuthorizationorRangerequest header. - A method other than
GETorHEAD, or an uncacheable status. - A higher-precedence header with a stricter policy. A
Vercel-CDN-Cache-Control: no-storewins overCDN-Cache-Control.
X-Easel-Cache is always MISS
The response is eligible but no entry matches. Check that repeated requests use the same hostname, path, and query string, and that the lifetime is longer than the time between requests. A new deployment or a purge also starts fresh. Responses above the cacheable size are never stored; see Limits.
Don't add a cache-busting query string while debugging. It creates a new cache key and hides the original problem.
A function runs when you expected a hit
Beyond the checks above, the framework may render the route dynamically. In Next.js, reading cookies or headers can opt a route out of static rendering. Middleware that changes the response or rewrites to a different path also changes what's cached. See the framework guides.
Too many cache variants
Look for tracking parameters and unique IDs in query strings. Each distinct URL is its own cache entry.
Content is stale
Old content after a purge or revalidation
Check that:
- The tag you invalidated exactly matches the tag on the response.
- You updated the data before invalidating. Otherwise the page regenerated from old data.
- The rendered page and the data it reads share a tag. If only the CDN entry was invalidated, the page regenerates from a still-fresh Runtime Cache or framework data-cache entry.
- You purged the right deployment. Production and preview caches are separate.
- The browser isn't using its own copy. A long browser
max-ageoutlasts any CDN purge.
expireTag seems to do nothing
The deployment must be storing values through getCache(), and the CDN
responses must carry the same tag. See Runtime Cache.
Visitors see an old version
A service worker or a stable URL with a long browser lifetime can keep serving old files. If the HTML still references an old hashed asset, the HTML is stale, not the asset; give HTML a revalidating policy. See Static files.
Cached data is shared between users
Set Cache-Control: private, no-store on user-specific routes, then purge.
Check for personalization from cookies or middleware on a route that has a
shared-cache policy. The CDN cache splits entries only by encoding, not by
other Vary headers, so a
response that depends on a request header, such as Accept-Language, needs
its own URL or no shared caching. Short lifetimes don't make
private content safe to cache. See Caching.
Static files
A file returns 404
Check that the file is in the build output, the output and root directories are right, the capitalization matches (paths are case-sensitive), and the URL includes your framework's base path or asset prefix. Test the deployment URL directly to rule out a production domain pointing at a different deployment.
An asset request runs a function or returns HTML
A broad rewrite, SPA fallback, or middleware matcher is catching the asset path. Exclude asset directories from catch-all rules and matchers.
A file has the wrong content type
Easel sets Content-Type from the file extension. Serve files with their
correct extension, and check for a custom header rule overriding it.
Compression is missing
Request with Accept-Encoding: br, gzip and look for Content-Encoding.
Compression is skipped when the client doesn't advertise it, the body is very
small, the type is already compressed or not text, the response already has
Content-Encoding, or it's a range request. Some proxies and tools advertise
only gzip.
A decoding error in the browser means the app set Content-Encoding on a body
it didn't encode, or the response was compressed twice. See
Compression.
Headers
A request header is missing in your code
Platform headers exist only on deployed requests that reach a function. Check the exact name in Headers and whether your framework passes the original request headers through.
A custom response header disappears
The name may be reserved by Easel or hop-by-hop, a framework may drop it, or it
may be set on a redirect hop instead of the final response. Use your own
prefix, not X-Easel-*, and check every hop with curl -IL.
Headers differ between preview and production
Previews add X-Robots-Tag, may sit behind
deployment protection, and can use
different environment variables.
Routing
A redirect is unexpected or loops
Follow the chain and read each Location:
Loops usually come from two layers enforcing the same rule: a trailing-slash
redirect that disagrees with your framework setting, middleware redirecting
into its own matcher, or a locale redirect adding and removing a segment.
Domain-level loops, such as apex and www pointing at each other, are covered
in Domain troubleshooting.
An external rewrite fails
Test the origin directly with curl -I, then through Easel, and compare
status and Location. Check that the origin resolves publicly, has a valid
certificate, accepts requests from Easel, gets any auth headers it needs, and
doesn't redirect back to your Easel URL.
An image returns an error or isn't optimized
Check your framework guide, that remote sources are in the framework's
allowlist (such as images.remotePatterns in Next.js), that the requested
width is one your configuration allows, and the source image's response
headers. See Image optimization.
Contact support
Include the full URL, the time with time zone, X-Easel-Id, X-Easel-Cache,
the status, whether preview, production, or both are affected, and whether a
proxy sits in front of Easel. Don't send cookies, tokens, or API keys.