javascriptnext.jsnext-auth

Middleware for next-auth and i18n (next.js 13 with app router)


I have next-auth set up (in my next.js 13 project with app router), it is working. Now I want to add internationalization to my app as described in the next.js docs. However, I don't know how to combine the two.

middleware working for next-auth

Every route in /user/ should be protected.

export { default } from "next-auth/middleware"
export const config = { matcher: ["/(user/.*)"] }

middleware for next-auth & i18n

This is what I've come up so far. I18n seems to be working, but the routes /user/... are not protected.

import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import { NextResponse } from 'next/server'

export { default } from "next-auth/middleware"

let locales = ['en', 'de']
let defaultLocale = 'en'

function getLocale(request) {
    let headers = { 'accept-language': 'en' }
    let languages = new Negotiator({ headers }).languages()
    return match(languages, locales, defaultLocale) // -> 'en'
}

export function middleware(request) {
    // Check if there is any supported locale in the pathname
    const pathname = request.nextUrl.pathname
    const pathnameIsMissingLocale = locales.every(
        (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
    )

    // Redirect if there is no locale
    if (pathnameIsMissingLocale) {
        const locale = getLocale(request)

        // e.g. incoming request is /products
        // The new URL is now /en/products
        return NextResponse.redirect(
            new URL(`/${locale}/${pathname}`, request.url)
        )
    }
}

export const config = {
    // '/(\w{2}/user/.*)' from nextauth (\w{2} because of /en/ or /de/); '/((?!_next).*)' from i18n
    matcher: ['/(\w{2}/user/.*)', '/((?!_next).*)'],
}

How do I combine the two?


Solution

  • I just figured it out thanks to the help of @mateen-kiani and phind.com! This is how I combined the two middlewares:

    import { NextResponse } from 'next/server'
    import { match } from '@formatjs/intl-localematcher'
    import Negotiator from 'negotiator'
    import nextAuthMiddleware from "next-auth/middleware"
    
    let locales = ['en', 'de']
    let defaultLocale = 'en'
    
    function getLocale(request) {
        let headers = { 'accept-language': 'en' }
        let languages = new Negotiator({ headers }).languages()
        return match(languages, locales, defaultLocale) // -> 'en'
    }
    
    export function middleware(request) {
        // cancel if exception
        const pathname = request.nextUrl.pathname
        const isException = ['/img', '/preview', '/icons', '/logo.svg', '/api', '/manifest.json', '/sw.js'].some((allowedPath) =>
            pathname.startsWith(`${allowedPath}`),
        );
        if (isException) return;
    
        // Check if there is any supported locale in the pathname
        const pathnameIsMissingLocale = locales.every(
            (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
        )
    
        // Redirect if there is no locale
        if (pathnameIsMissingLocale) {
            const locale = getLocale(request)
            return NextResponse.redirect(
                new URL(`/${locale}/${pathname}`, request.url)
            )
        }
    
        // check if auth is required
        if (pathname.includes("/user")) {
            // check & handle if logged in
            const nextAuthResponse = nextAuthMiddleware(request)
            if (nextAuthResponse) {
                return nextAuthResponse
            }
        }
    
        // Continue if no NextAuth middleware response
        return NextResponse.next()
    }
    
    export const config = {
        matcher: [
            '/((?!_next).*)',
        ],
    }