Queue · 0 items

A cookie-based locale redirect got cached by Firebase CDN without Vary: Cookie, poisoning Googlebot with 307 redirects. Here's how I diagnosed and fixed it.
noindex tags or a broken robots.txt. Both turned out fine. The real problem was far more subtle — and honestly, I didn't expect a locale cookie to be the culprit.
/id/curl against the live homepage to check response headers:
curl -sI https://snipgeek.com/ | grep -iE 'HTTP|location|cache|cdn'HTTP/2 307
location: /id/
cache-control: public, s-maxage=3600, stale-while-revalidate=86400
cdn-cache-status: hit
age: 2326
/id/ — the Indonesian locale — for every single visitor, including Googlebot. And the CDN had cached it for over 38 minutes already.
I checked other pages like /blog and /about — those returned 200 normally. But the homepage, the most important page for crawling, was broken.
proxy.ts (Next.js 16's replacement for middleware).
Here's the code that caused the issue:
// proxy.ts (BEFORE — the broken version)
const preferredLocale = request.cookies.get("NEXT_LOCALE")?.value;
if (pathnameIsMissingLocale) {
if (preferredLocale && preferredLocale !== i18n.defaultLocale) {
// This redirect gets cached by the CDN!
return NextResponse.redirect(
new URL(`/${preferredLocale}${pathname}`, request.url),
);
}
return NextResponse.rewrite(
new URL(`/${i18n.defaultLocale}${pathname}`, request.url),
);
}NEXT_LOCALE=id cookie, redirect them to the Indonesian version. Otherwise, rewrite to the default English locale.
The problem is what happens after the redirect response leaves the server.
next.config.ts applied this cache header to all page routes:
{
key: "Cache-Control",
value: "public, s-maxage=3600, stale-while-revalidate=86400",
}Vary: Cookie header.
So when I (or any Indonesian-preferring visitor) hit the homepage with a NEXT_LOCALE=id cookie, the CDN cached the 307 redirect. Every subsequent visitor — including Googlebot — got that cached redirect instead of the actual English homepage.
Googlebot doesn't send cookies. It expected to see HTML content at snipgeek.com/. Instead, it got bounced to /id/ over and over, depending on CDN cache timing. The result: Google stopped trusting the homepage as a stable entry point, and impressions collapsed.
Vary: Cookie = cache poisoning. The first visitor's cookie determines what everyone else sees until the cache expires.robots.txt allowed / for all user agents, and sitemap.xml contained all 216 published URLs including every blog post. I later noticed one SEO hardening gap, though: robots.txt was also blocking /_next/, which is too aggressive for a Next.js site because Google may need those assets to render pages properly. I fixed that afterward.curl -sS https://snipgeek.com/robots.txt | head -20
curl -sS https://snipgeek.com/sitemap.xml | grep -c '<url>'noindex directives:curl -sS https://snipgeek.com/blog/disable-windows-defender-windows-11 \
| grep -i 'robots'<meta name="robots" content="index, follow"/> — correct. No X-Robots-Tag header either.curl -sI -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://snipgeek.com/blog/disable-windows-defender-windows-11age: 2326 and cdn-cache-status: hit headers confirmed it wasn't a fresh response from the server. The CDN was serving a stale redirect. I traced it back to proxy.ts and found the cookie-based redirect branch.proxy.ts:
// proxy.ts (AFTER — the fixed version)
if (pathnameIsMissingLocale) {
return NextResponse.rewrite(
new URL(
`/${i18n.defaultLocale}${pathname.startsWith("/") ? "" : "/"}${pathname}`,
request.url,
),
);
}LanguageSwitcher component and the LocaleSuggestionBanner both use router.push() to navigate to the /id/ URL when the user switches languages. The cookie is still set for the banner's "don't show again" logic, but the proxy never reads it.
Vary: Cookie (which effectively disables CDN caching) or move the logic client-side./en redirect from next.config.ts — the proxy already canonicalizes /en/* → /* with a 308 redirect. Having both was redundant.robots.txt for Next.js assets — I stopped disallowing /_next/ so crawlers can fetch the CSS/JS needed to render pages correctly.robots meta to blog and note generateMetadata — previously relied on layout inheritance, which is fragile if the layout metadata changes./id/ — exactly as designed). No error logs, no 500s, no build failures.
The only signal was the Search Console graph dropping to zero. If I hadn't checked GSC that week, it could have gone unnoticed for much longer.
Three takeaways I'm keeping pinned:
public cache headers. If the response varies by cookie, either mark it private or use Vary: Cookie.curl, not a browser. Browsers carry cookies and cache state that hide CDN-level issues.curl -sI your-homepage.com right now. You might be surprised by what comes back.
Explore related topics and continue reading similar content.
Preparing the comments area...