What this usually means
This class of issues comes from the intersection of HTTP-only cookie attributes, mismatched server/client origins, and Next.js routing strategy. Problems most often stem from misconfigured SameSite or Secure attributes, API routes deployed on Vercel vs. custom servers, or differences in how cookies are set during SSR or from client-side code. CORS headers, proxy setups, and even clock skew can all conspire to prevent cookies from being set, with the browser often providing little or no warning.
The first ten minutes — establish facts before touching code.
- 1Reproduce the login and inspect the network tab for a 'Set-Cookie' response header after your auth endpoint (it must be present).
- 2Check the cookie attributes in the response: is Secure set correctly for your protocol, and is SameSite compatible with your login flow?
- 3Confirm both server and client are on the same domain and port, or that you set credentials: 'include' and correct CORS headers for cross-origin.
- 4If you’re using getServerSideProps or API routes, verify they’re running on the origin you expect (localhost vs production domain).
- 5Try toggling between HTTP and HTTPS in your dev and prod environments to isolate Secure-related issues.
- 6Temporarily remove HttpOnly or change SameSite to 'Lax' or 'None' to see if the cookie appears (then revert for security).
The specific files, logs, configs, and dashboards that usually own this bug.
- searchpages/api/auth/[...nextauth].js (or .ts) for cookie config
- searchnext.config.js for rewrites, proxy, and domain settings
- searchVercel dashboard’s environment variables and custom domain settings
- searchBrowser DevTools > Application > Cookies for your site
- searchAPI route response headers in DevTools > Network after login
- searchServer logs (e.g., stdout on Vercel or custom Node logs) for authentication or header errors
- search.env files for NEXTAUTH_URL and related cookie domain config
Practical causes, not theory. These are the things you will actually find.
- warningSameSite set to 'Strict' or 'Lax' when cross-site POST is required
- warningSecure:true on cookies but using HTTP (not HTTPS) in dev
- warningCookie domain set incorrectly—should match the browser-visible domain exactly
- warning'Set-Cookie' sent from an API route under a different subdomain or port
- warningProxy or rewrite stripping Set-Cookie headers
- warningMissing 'credentials: include' in fetch/Axios when calling login endpoint
Concrete fix directions. Pick the one that matches your root cause.
- buildSet SameSite to 'None' and Secure to true for cross-origin (OAuth) logins
- buildEnsure the cookie domain exactly matches the public domain (omit .example.com if not needed)
- buildAdd 'credentials: include' to fetch/Axios requests that expect cookies
- buildSwitch to HTTPS for local development if Secure is required
- buildConfigure CORS properly on API routes and ensure allow credentials is true
- buildOn Vercel, set NEXTAUTH_URL and any relevant environment variables correctly
A fix you cannot prove is a guess. Close the loop.
- verifiedAfter login, confirm the cookie appears in DevTools > Application > Cookies
- verifiedThe 'Set-Cookie' header is present in the login response and not stripped by any proxy
- verifiedReload the page and see a logged-in state persist
- verifiedInspect API responses to verify authenticated endpoints return 200, not 401/403
- verifiedTest both dev and production environments for identical behavior
- verifiedTry login from an incognito window and cross-origin scenario
Things that make this bug worse or harder to find.
- warningSetting Secure:true without HTTPS in all environments
- warningAssuming SameSite=Lax will work for OAuth or cross-site flows
- warningForgetting to set 'credentials: include' on client requests
- warningBlindly copying cookie config from a different stack (e.g., Express/React)
- warningIgnoring domain/subdomain mismatches between API and client
- warningOverlooking browser console warnings—some browsers now log subtle cookie errors
Next.js Login Succeeds but Cookie Never Set in Production
Timeline
- 09:02User reports they cannot stay logged in after authenticating via Google.
- 09:10Engineer tests in dev (localhost:3000) and sees everything working.
- 09:17Production login tested via Chrome DevTools—no auth cookie appears post-login.
- 09:20Network tab shows 'Set-Cookie' header missing from the login response.
- 09:27Review of [...nextauth].js reveals Secure:true and SameSite:'lax' config.
- 09:31Realize site is accessed under https://app.mycompany.com, but NEXTAUTH_URL was set to http://localhost:3000 in Vercel env vars.
- 09:39Update NEXTAUTH_URL to match production domain and set SameSite:'none', Secure:true.
- 09:43Redeploy, retest—cookie now appears and login persists.
I got the bug report from product: users were able to log in with Google OAuth, but after redirecting back, the app would always show them as logged out. Reproducing locally, everything worked, so I suspected a deployment-specific issue.
In production, I checked the login POST response in Chrome DevTools: there was no 'Set-Cookie' header at all. The cookie config was set to Secure:true (so only HTTPS) and SameSite:'lax'. More importantly, our Vercel NEXTAUTH_URL was still pointing to localhost.
After updating NEXTAUTH_URL to our real production domain and adjusting SameSite:'none', Secure:true, I redeployed. Now, login worked seamlessly—the auth cookie set as expected. The issue was a subtle mismatch in env config between dev and prod.
Root cause
NEXTAUTH_URL env variable did not match the deployed production domain, causing the auth cookie to be dropped.
The fix
Set NEXTAUTH_URL to the public domain, ensured SameSite:'none' and Secure:true for cross-origin OAuth.
The lesson
Cookie and domain config must align perfectly between environments—assume nothing between dev and prod.
If your login endpoint is behind a proxy or Next.js rewrite, double-check that 'Set-Cookie' headers are not being stripped. Some reverse proxies, especially custom nginx or old vercel.json rewrites, drop headers by default unless explicitly passed.
For Next.js API routes, the cookie-setting logic must run on the same domain (and ideally the same subdomain/port) as the browser expects. Otherwise, the browser will drop the cookie without warning.
OAuth flows (Google, GitHub) redirect to your domain, which sometimes triggers cross-origin cookie restrictions. If SameSite is anything but 'none', browsers will refuse to set the auth cookie post-redirect.
Always test OAuth on your actual production domain, not just localhost, as the cookie policy logic is enforced differently depending on perceived origins.
An issue that doesn't appear locally but shows up in production is almost always a mismatch in env vars, domain, protocol, or cookie attributes. Dev is usually on HTTP and localhost, which can mask Secure or domain-dependent bugs.
Always compare cookie settings and domain structure between local and prod. Explicitly set NEXTAUTH_URL and any relevant domain config for each environment, and check your deployment dashboard for accidental misconfigurations.
Frequently asked questions
Why does my auth cookie work on localhost but not in production?
Localhost often runs HTTP, so Secure cookies are accepted. In production, HTTPS and domain mismatches, or incorrect NEXTAUTH_URL and SameSite attributes, cause cookies to be dropped.
What SameSite value should I use for Next.js authentication cookies?
Use SameSite:'lax' for direct logins on the same domain. For OAuth or any cross-origin, set SameSite:'none' and Secure:true. Otherwise, cookies won't be set after cross-origin redirects.
How can I see if the Set-Cookie header is actually sent?
Open Chrome DevTools Network tab, filter by your login/auth route, and check the Response Headers for 'Set-Cookie'. If missing, your server isn’t sending it or a proxy is stripping it.
Does getServerSideProps affect cookie behavior?
Yes—SSR and API routes must run under the same domain and protocol as the browser. If you SSR from a different origin or proxy, cookies may be lost.