next.jsinternationalizationnext-intl

How to fix redirects in Next.js middleware?


I'm building a project using next.js and next-intl plugin for internationalization.

I want to extend next-intl locale detection logic by forcing the use of our own cookies (that were used before on this project).

The problem is when I chain my custom middleware in front of next-intl middleware it does not seem to work. Looks like they are getting overwritten by a middleware in chain

my middleware - handleLocaleRedirect.ts:

export default async function handler(request: NextRequest) {
...
const newUrl = new URL(newPathname)

return NextResponse.redirect(newUrl)
}

handleI18NRouting.ts:

import { routing } from '../i18n/routing'
import createMiddleware from 'next-intl/middleware'

const handleI18nRouting = createMiddleware(routing)

export default async function handler(request: NextRequest) {
  const response = handleI18nRouting(request)

  // Remove excessive alternate links
  const link = LinkHeader.parse(response.headers.get('link') || '')
  link.refs = link.refs.filter((entry) =>
    STANDARD_LOCALES.includes(entry.hreflang)
  )
  response.headers.set('link', link.toString())

  return response
}

middleware.ts:

import { pagesWhiteListMiddleware } from './middlewares/pagesWhiteList'
import { chain } from '@nimpl/middleware-chain'
import handleI18nRouting from './middlewares/handleI18nRouting'
import handleLocaleRedirect from './middlewares/handleLocaleRedirect'

export default chain([
  handleLocaleRedirect,
  handleI18nRouting, // <-- here the page is getting redirected again, forcing next-intl logic
  pagesWhiteListMiddleware,
])

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|next-assets|favicon.ico|sw.js|site.webmanifest).*)',
  ],
}

How to force my redirect?


Solution

  • The solution became clear after I stated the question like that ^

    The final request is only returned to the client after the whole chain is complete.

    This means that interim NextResponse.redirect won't work in case there are more redirects afterwards.

    The solution was to place the redirect logic inside next-intlmiddleware configuration and remove it from the middlewares chain:

    handleI18NRouting.ts:

    const handleI18nRouting = createMiddleware(routing)
    
    export default async function handler(request: NextRequest) {
      const newUrl = await handleLocaleRedirect(request)
    
      if (newUrl) {
        const resp = NextResponse.redirect(newUrl)
        resp.cookies.set(LANGUAGE_MODAL_COOKIE, 'true')
        return resp
      }
      const response = handleI18nRouting(request)
    
      // Remove excessive alternate links
      const link = LinkHeader.parse(response.headers.get('link') || '')
      link.refs = link.refs.filter((entry) =>
        STANDARD_LOCALES.includes(entry.hreflang)
      )
      response.headers.set('link', link.toString())
    
      return response
    }
    

    UPDATE:

    Also since I was using the @nimpl/middleware-chain it's really important to return FinalNextResponse in case you want to redirect before hitting the rest of the middlewares:

    middleware.ts:

    import { pagesWhiteListMiddleware } from './middlewares/pagesWhiteList'
    import { chain, FinalNextResponse } from '@nimpl/middleware-chain'
    import handleI18nRouting from './middlewares/handleI18nRouting'
    import { NextResponse } from 'next/server'
    
    export default chain([
      handleI18nRouting,
      function (req) {
        if (req.summary.type === 'redirect') return FinalNextResponse.next() // <-- this will break the chain and return the actual response from the previous chain middleware
        return NextResponse.next()
      },
      pagesWhiteListMiddleware,
    ])
    
    export const config = {
      matcher: [
        '/((?!api|_next/static|_next/image|next-assets|favicon.ico|sw.js|site.webmanifest).*)',
      ],
    }