Next.js Middleware/Proxy redirect loop at /login in Production, but works perfectly on npm start locally #96332
SummaryHi everyone,
The Bug: Has anyone encountered this specific environment mismatch between local npm start and live hosting environments? Could this be related to Edge runtime deployment restrictions, or how Next.js handles production cookie headers differently than localhost? Any help or pointers would be greatly appreciated! Additional informationNo response ExampleNo response |
Replies: 6 comments 11 replies
|
This looks more like a matcher or Amplify configuration issue than a cookie issue. Make sure /login is included in your matcher and avoid redirecting to the current route: export function proxy(request: NextRequest) {
const path = request.nextUrl.pathname
const token = request.cookies.get("session")?.value
if (token && path === "/login") {
return NextResponse.redirect(new URL("/dashboard", request.url))
}
if (!token && path.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ["/login", "/verify-otp", "/dashboard/:path*"],
}The [Next.js Proxy docs](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) say: “The matcher option allows you to target specific paths for the Proxy to run on.” Also ensure the session cookie has path: "/". Then check Amplify’s Rewrites and redirects section for a /login or SPA catch-all rule. One more important point: [Amplify currently documents support only through Next.js 15](https://docs.aws.amazon.com/amplify/latest/userguide/ssr-amplify-support.html). If you use Next.js 16 with proxy.ts, the production environment may not fully support it yet. |
|
Do you have any logs or headers you see during the navigation? Often when Are you able to curl this endpoint while pretending to be logged in as well? Like login, and then in the Chrome Dev tools you can copy a request as |
|
This is a classic issue that occurs because of how production Edge CDNs (like AWS Amplify or CloudFront) handle cookies and caching differently than your local Node.js server. When you test locally ( Here are the two steps to fix this loop: 1. Fix the Middleware Caching LoopUpdate your import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session_token')?.value;
const { pathname } = request.nextUrl;
// 1. User is logged in, but tries to access public auth pages (/login, /verify-otp)
if (token && (pathname === '/login' || pathname === '/verify-otp' || pathname === '/')) {
const response = NextResponse.redirect(new URL('/dashboard', request.url));
// CRITICAL: Prevent Edge CDN caching of this redirect response
response.headers.set('x-middleware-cache', 'no-cache');
return response;
}
// 2. User is NOT logged in, but tries to access protected dashboard pages
if (!token && pathname.startsWith('/dashboard')) {
const response = NextResponse.redirect(new URL('/login', request.url));
response.headers.set('x-middleware-cache', 'no-cache');
return response;
}
return NextResponse.next();
}
export const config = {
// Ensure middleware excludes internal assets but targets your routes perfectly
matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'],
};2. Configure AWS Amplify to Forward CookiesBy default, AWS Amplify might strip or ignore incoming headers/cookies before sending the request downstream to your Next.js serverless architecture.
|
|
No need to share your full cURL command here! Instead, you just need to ensure your AWS Amplify or Edge CDN configuration is explicitly forwarding your authentication cookies downstream. If the Edge layer strips the cookies, the middleware will always read them as Try updating your |
|
Two things nobody's flagged yet. First — you mentioned you're on Next.js 16. In 16, middleware.ts was renamed to proxy.ts with an exported proxy() function. If you've been editing middleware.ts, that file isn't running, which would explain why the fix above changed nothing. Worth confirming which filename you actually have. Second — x-middleware-cache: no-cache won't help here. That was an internal for the client router cache, not a CDN header, and CloudFront ignores it. To confirm what's happening, following on from @icyJoseph's request: hit /login while logged in with devtools open, preserve log, and check the response headers on the /login document request. If you see x-cache: Hit from cloudfront, the edge is serving cached static HTML and your proxy never ran. /login has no dynamic markers so Next prerenders it, and Amplify caches it with cookies stripped from the cache key. If that's the case, two fixes: Force /login dynamic so it's never edge-cached — add export const dynamic = 'force-dynamic' to app/login/page.tsx. Also worth noting Amplify's docs currently list support through Next.js 15, so Next 16 + proxy.ts may have rough edges there regardless. |
|
I think there is one important variable here that is worth isolating before AWS currently documents Amplify Hosting compute support for Next.js versions That would also explain why:
I would test this before making additional authentication changes:
You can also verify the Proxy independently with a request containing the real curl -I The expected response should be a redirect to If Next.js 15 redirects correctly while the identical Next.js 16 deployment I'd also avoid relying on Your matcher itself appears broad enough to cover So my debugging order would be: Next.js 15 on Amplify Given that AWS currently only documents support through Next.js 15, I would |
Two things nobody's flagged yet.
First — you mentioned you're on Next.js 16. In 16, middleware.ts was renamed to proxy.ts with an exported proxy() function. If you've been editing middleware.ts, that file isn't running, which would explain why the fix above changed nothing. Worth confirming which filename you actually have.
Second — x-middleware-cache: no-cache won't help here. That was an internal for the client router cache, not a CDN header, and CloudFront ignores it.
To confirm what's happening, following on from @icyJoseph's request: hit /login while logged in with devtools open, preserve log, and check the response headers on the /login document request. If you see x-cache: Hit from…