TL;DR — Creating a post with a placeholder slug, then correcting it via an update call, silently created a site-wide 301 redirect from the old slug to the new one — because the CMS auto-generates a redirect any time a content update changes slug. When the placeholder slug happened to match a real, live, unrelated post's URL, that redirect hijacked it: visitors to the real English article were bounced to the newly-created Indonesian one.
A first translation batch created an Indonesian post by reusing the English post's slug, intending to change it to a proper localized slug right after via handleContentUpdate. The update succeeded — but so did an automatic redirect creation nobody asked for.
The content-update handler auto-creates a site-wide, locale-unaware redirect whenever the incoming slug differs from the row's current slug. This is meant to protect a post's own SEO when you rename it — but it doesn't check whether that old slug value is still in active use by a completely different post. Since the Indonesian draft briefly held the exact slug string of the live English post, the redirect rule captured that value and pointed it at the new location instead.
The fix
Delete the bad redirect row: DELETE FROM _emdash_redirects WHERE group_name = 'Auto: slug change' AND from_path = '/original-english-post-slug'.
A raw SQL delete does not clear the running production process's in-memory redirect cache (module-level, no TTL, only invalidated by the app's own redirect API handlers) — either restart the process, or add-then-delete a throwaway redirect through the real admin UI, which hits the live API and flushes that same process's cache.
Lessons learned
Decide a translated post's final slug before the very first handleContentCreate call — never reuse the source locale's slug as a placeholder.
Never change a post's slug after creation if you can avoid it; if you must, immediately check whether the old slug value collides with anything else live before trusting the auto-redirect is harmless.
A quick curl -sI against the source post's own URL is a cheap sanity check to run right after any slug-touching operation.
TL;DR — A custom session-cookie login flow appeared to succeed on localhost (the OTP verified, the response looked fine) but every subsequent request to a login-gated page treated the visitor as logged out. Identical code worked fine on the live HTTPS site. The cookie's Secure attribute was hardcoded to true — and per the cookie spec, browsers never store or send a Secure cookie over a plain, non-HTTPS connection.
Check the actual Set-Cookie response header and the browser's own cookie storage panel — on localhost over http://, the cookie is sent by the server but never actually stored by the browser.
Root cause
// before -- assumes the app is always served over HTTPS
setCookie("session", token, { secure: true, httpOnly: true });
emdashkits.com
A cookie config that quietly assumes "we're always on HTTPS" breaks the instant you test over plain HTTP, which local dev servers commonly are.
// after -- derive secure from the actual request protocol
const isHttps = request.url.startsWith("https://");
setCookie("session", token, { secure: isHttps, httpOnly: true });
emdashkits.com
Lessons learned
Any Secure-flagged cookie needs to key off the real request scheme, not an assumption baked in once at cookie-creation time.
"Works in production, silently fails in local dev" is a strong signal to check cookie flags before anything else in an auth flow.
Check other cookies in the same codebase for the same hardcoded assumption — if one cookie has this bug, sibling cookies set the same way are worth auditing too.
TL;DR — GA4's gtag.js snippet was installed correctly, the page loaded with no visible JavaScript error, and GA4's real-time report still showed zero activity. The CMS's default Content-Security-Policy had no allowance for analytics domains and no config option to add one — so every request to Google's tracking endpoints was blocked at the browser level before it could fail loudly.
Check the browser's dedicated CSP violation reporting, not the regular console error list — CSP blocks are reported through their own channel, not thrown as normal script errors, so "no console errors" doesn't mean nothing was blocked.
Root cause
The CSP's script-src and connect-src directives had no entry for googletagmanager.com or google-analytics.com, and the CMS exposed no configuration surface to add one — the only way in was patching the CSP directives directly.
// patch-package: add analytics domains to the existing CSP directives
scriptSrc.push("https://www.googletagmanager.com");
connectSrc.push("https://www.google-analytics.com", "https://www.googletagmanager.com");
"No console errors" is not proof nothing was blocked — CSP violations live in their own reporting surface and are easy to miss if you're only scanning for red error text.
Before adding any third-party script tag to a site with a CSP already in place, check the CSP's directives first rather than assuming a silently-empty analytics dashboard means a snippet-installation mistake.
Comments