The Problem:
A user has a Next.js 13 project with App Router and NextAuth set up. They aim to incorporate internationalization (i18n) using the method outlined in the Next.js documentation. The challenge arises in merging the middleware for NextAuth, which protects specific routes, with the middleware for i18n, which handles locale detection and redirection. The existing i18n middleware appears functional, but the NextAuth routes are no longer protected. The user seeks assistance in combining these two middleware solutions effectively.
The Solutions:
Solution 1: Combine next-auth and i18n middleware
The provided code combines the middleware for next-auth and i18n by first handling the i18n logic and then checking if authentication is required using the next-auth middleware.
Here’s a breakdown and explanation:
-
Importing Necessary Libraries:
import { NextResponse } from 'next/server'; import { match } from '@formatjs/intl-localematcher'; import Negotiator from 'negotiator'; import nextAuthMiddleware from "next-auth/middleware";
-
Defining Locale-Related Variables:
let locales = ['en', 'de']; let defaultLocale = 'en';
locales
: An array of supported locales.defaultLocale
: The default locale to use if no preferred locale is found.
-
getLocale Function:
function getLocale(request) { let headers = { 'accept-language': 'en' }; let languages = new Negotiator({ headers }).languages(); return match(languages, locales, defaultLocale); // -> 'en' }
- This function determines the preferred locale based on the
Accept-Language
header in the request and returns the matched locale.
- This function determines the preferred locale based on the
-
middleware Function:
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(); }
- This function is the central middleware that handles both i18n and next-auth.
- It first checks if the request path is an exception (e.g., image files, API endpoints, etc.) and skips further processing if it is.
- If no locale is found in the pathname, it determines the preferred locale using
getLocale
and redirects the request to the correct version (e.g.,/en/products
). - If the path includes "/user", it checks if authentication is required using the
nextAuthMiddleware
. If authentication is required, it returns the response fromnextAuthMiddleware
. - If authentication is not required or already handled by
nextAuthMiddleware
, it continues the request by returningNextResponse.next()
.
-
config Object:
export const config = { matcher: [ '/((?!_next).*)', ], };
- This configures the middleware to match all routes except those that start with
/_next
. This ensures that the middleware is applied to all relevant pages in the application.
- This configures the middleware to match all routes except those that start with
By combining the i18n and next-auth middleware in this manner, you can ensure that users are redirected to the correct locale version of the site and that authentication is handled appropriately for protected routes.
Solution 2: Using NextAuth and Next-i18n-Router Middleware
To combine the NextAuth and i18n middleware effectively, follow these steps:
-
Install Dependencies:
- Install
next-auth
andnext-i18n-router
:npm install next-auth next-i18n-router
- Install
-
Configure Middleware:
-
Create a
middleware.ts
file to handle authorization and i18n:import NextAuth from "next-auth"; import { authConfig } from "./auth.config"; export default NextAuth(authConfig).auth; export const config = { matcher: "/((?!api|static|.*\\..*|_next).*)", };
-
-
Create
auth.config.ts
File:-
This file will combine the NextAuth and i18n configurations:
import { i18nRouter } from "next-i18n-router"; import type { NextAuthConfig } from "next-auth"; import { i18nConfig } from "@/locale/config"; const getLocaleFromPath = (pathname: string) => { const localeFromPathRegex = new RegExp(`^/(${i18nConfig.locales.join("|")})?`); const localeFromPath = pathname.match(localeFromPathRegex)?.[1]; return { locale: localeFromPath, path: localeFromPath ? `/${localeFromPath}` : "" }; }; const checkCurrentRoute = (pathname: string, locale?: string) => { const checkPathnameRegex = (pattern: string | RegExp) => { const rootRegex = new RegExp(pattern); return Boolean(pathname.match(rootRegex)); }; return { root: checkPathnameRegex(`^/(${locale})?$`), dashboard: checkPathnameRegex(`^(/${locale})?/dashboard.*`), login: checkPathnameRegex(`^(/${locale})?/login.*`), }; }; export const authConfig = { pages: { signIn: "/login", }, callbacks: { authorized({ auth, request }) { const { nextUrl } = request; const locale = getLocaleFromPath(nextUrl.pathname); const dashboardUrl = new URL(`${locale.path}/dashboard`, nextUrl); const { root: isOnRoot, dashboard: isOnDashboard, login: isOnLogin } = checkCurrentRoute(nextUrl.pathname, locale.locale); const isLoggedIn = !!auth?.user; if (isOnRoot || (isLoggedIn && !isOnDashboard)) { // If on root or logged in but not on dashboard, redirect to dashboard return Response.redirect(dashboardUrl); } if ((isOnLogin && !isLoggedIn) || (isOnDashboard && isLoggedIn)) { // Not logged in but on login OR logged in and on dashboard -> allow access return i18nRouter(request, i18nConfig); } // Not logged in and not on login or dashboard -> redirect to login page return false; }, }, providers: [], } satisfies NextAuthConfig;
-
-
Create
@/locale/config.ts
File:-
This file will contain the i18n configuration:
export const i18nConfig = { locales: ["en", "es"], defaultLocale: "en", };
-
-
Configure Middleware in
pages/_middleware.js
:-
Add the
middleware
function to thepages/_middleware.js
file:export { default } from "next-auth/middleware";
-
With this setup, you can effectively combine NextAuth for user authentication and next-i18n-router
for internationalization in your Next.js 13 application.
Solution 3: Route and locale-based URL redirection
Create a single middleware file and specify two different matchers in the middleware configuration. The first matcher should match all requests that need to be protected by NextAuth, and the second matcher should match all requests that need to be redirected to the correct locale.
Inside the middleware function:
- Check if the pathname contains a supported locale. If not, redirect to the default locale.
- Determine the user’s locale and set it as the request’s pathname.
- Return the modified request object.
In the middleware.js
file:
export function middleware(request) {
let locales = ['en', 'fr'];
let defaultLocale = 'en';
const isPathAllowed = [
'/img', 'img', '/api'
].some(allowedPath =>
pathname.startsWith(`${allowedPath}`)
);
if (isPathAllowed) return;
// Redirect if there is no locale
const locale = defaultLocale;
request.nextUrl.pathname = `/${locale}${pathname}`;
return Response.redirect(request.nextUrl);
}
export const config = {
matcher: [
'//((?!_next).*)//',
'//(/)//'
],
};
This middleware will redirect any request that does not have a supported locale in the pathname to the default locale. It will also redirect any request to the root URL to the default locale.
In your messages.js
file, define the localized strings for each language:
import en from './en.json';
import fr from './fr.json';
export const getSelectedLanguage = (lang) => {
switch (lang) {
case 'en':
return en;
case 'fr':
return fr;
default:
return null;
}
};
In your layout component (layout.js
), use the NextIntlClientProvider
to provide the selected language and localized strings to the child components:
import { SessionProvider } from 'next-auth/react';
import { NextIntlClientProvider } from 'next-intl';
import { getSelectedLanguage } from '@/internationalization/messages/messages';
export default function RootLayout({ children, params: { lang } }) {
const messages = getSelectedLanguage(lang);
return (
<html lang={lang}>
<body>
<NextIntlClientProvider locale={lang} messages={messages}>
<SessionProvider>{children}</SessionProvider>
</NextIntlClientProvider>
</body>
</html>
);
}
In your pages, use the useLocale
and useTranslations
hooks from next-intl
to access the current locale and localized strings:
import Link from 'next/link';
import { useLocale, useTranslations } from 'next-intl';
const HomePage = () => {
const t = useTranslations('Index');
const locale = useLocale();
return (
<>
<main>
<div className="container">
<h1>{t('title')}</h1>
</div>
</main>
</>
);
}
With this setup, your application will automatically redirect users to the correct locale based on their preferred language, and it will provide localized strings for the user interface.
Q&A
Combine next-auth & i18n (next.js 13) middleware to protect routes and enable internationalization.
Try leveraging NextAuth’s middleware configuration. Mix authorized handler and i18nRouter in the auth.config file.
I’ve tried combining next-auth & i18n middleware, but only i18n seems to be working. Why?
Make sure the paths for both middlewares don’t overlap. Use a different matcher regex for each middleware.
What if I want to protect the /user path and its sub-routes but allow other routes?
Use the matcher configuration in the next-auth middleware to specify the protected paths.
Video Explanation:
The following video, titled "Chain NextAuth and Internationalization Middlewares in NextJs 14 ...", provides additional insights and in-depth exploration related to the topics discussed in this post.
This video will look at chaining NextAuth and Internationalization (i18n) middleware functions in NextJs to run one after the other, ...
The following video, titled "Chain NextAuth and Internationalization Middlewares in NextJs 14 ...", provides additional insights and in-depth exploration related to the topics discussed in this post.
This video will look at chaining NextAuth and Internationalization (i18n) middleware functions in NextJs to run one after the other, ...