reactjsnext.jsnext-authnextjs14next-intl

NextJS + next-intl middleware fetch


if the ip address of the user entering the site is blocked, I want to redirect to a different page such as /blocked and not access the site. I wanted to create a fetch request on my middleware and run other codes according to the result, but next-intl gave the error “Unable to find next-intl locale because the middleware didn't run on this request.”

what do I need to do to control banned users via api? waiting for your advice

import createMiddleware from 'next-intl/middleware';
import { defaultLocale, locales, pathnames } from "./config/lang-config";
import withAuth from 'next-auth/middleware';
import { NextRequest, NextResponse } from 'next/server';
import { getProfile } from './services/authQueries';

const privatePages = [
    '/profile',
    '/settings',
    '/settings/*',
    '/wallet',
    '/wallet/*',
    '/auth/logout',
];

const intlMiddleware = createMiddleware({
    defaultLocale,
    locales,
    localePrefix: "as-needed",
    pathnames,
    localeDetection: true,
});

const authMiddleware = withAuth(
    // Note that this callback is only invoked if
    // the `authorized` callback has returned `true`
    // and not for pages listed in `pages`.
    function onSuccess(req) {
        return intlMiddleware(req);
    },
    {
        callbacks: {
            authorized: async ({ token }: { token: any }) => {
                const accessToken = token?.tokenData?.token || token?.token as string;
                if (token) {
                    try {
                        const res = await getProfile(accessToken as string, 'en');
                        if (res.isSucceed) {
                            token.user = res.data;
                            return true;
                        } else {
                            throw new Error(res.message);
                        }
                    } catch (error) {
                        if (error instanceof Error) {
                            console.error(error.message);
                        } else {
                            console.error('An unknown error occurred');
                        }
                        return false;
                    }
                } else {
                    return false;
                }
            },
        },
        pages: {
            signIn: '/',
        }
    }
);

const env = process.env.NODE_ENV;

const blockedCountries = ['US', 'UK'];

export default function middleware(req: NextRequest) {
    const res = NextResponse.next()
    const pathname = req.nextUrl.pathname

    // Skip this middleware in development or if the path is international
    if (env !== 'development' && pathname !== '/blocked') {

        const country = req.geo?.country || req.headers.get('cloudfront-viewer-country') || req.headers.get('x-vercel-ip-country');

        if (blockedCountries.includes(country ?? '')) {
            return NextResponse.redirect(new URL('/blocked', req.url))
        }
    }


    const privatePathnameRegex = RegExp(
        `^(/(${locales.join('|')}))?(${privatePages
            .flatMap((p) => {
                // for '*'
                return p.replace(/\*/g, '.*');
            })
            .map((p) => (p === '/' ? ['', '/'] : p))
            .join('|')})/?$`,
        'i'
    );

    const isPrivatePage = privatePathnameRegex.test(req.nextUrl.pathname);

    if (!isPrivatePage) {
        const country = req.geo?.country || req.headers.get('cloudfront-viewer-country') || req.headers.get('x-vercel-ip-country');

        const response = intlMiddleware(req);
        response.cookies.set("client-counry", country || '');
        return response;
    } else {
        return (authMiddleware as any)(req);
    }
}


export const config = {
    matcher: [
        // Enable a redirect to a matching locale at the root
        '/',

        // Set a cookie to remember the previous locale for
        // all requests that have a locale prefix
        '/(en-US)/:path*',

        // Enable redirects that add missing locales
        // (e.g. `/pathnames` -> `/en/pathnames`)
        '/((?!api|_next|_vercel|.*\\..*).*)'
    ]
};

I sent an API request on Middleware and redirected the user with NextResponse according to the incoming data, but I got the following error "Unable to find next-intl locale because the middleware didn't run on this request."


Solution

  • The error "Unable to find next-intl locale because the middleware didn't run on this request" occurs because Next.js middleware runs on the edge, and next-intl expects the middleware to be applied consistently to determine the locale for each request. This can be tricky when combining redirection, locale detection, and user authorization within a middleware file.

    To handle blocked users with next-intl and authentication while avoiding this error, you can refactor the middleware to manage country-based redirects separately from locale-based processing. Here’s how to do this:

    Step 1: Separate Blocked User Logic from next-intl Middleware

    First, we’ll move the blocked user check to the top of the middleware. This way, it executes before next-intl tries to detect the locale or apply its configurations.

    Step 2: Ensure intlMiddleware Runs Consistently

    The intlMiddleware should be the only function responsible for handling localization. This approach makes sure it’s applied consistently to every request, resolving the locale-related error.

    import createMiddleware from 'next-intl/middleware';
    import { defaultLocale, locales, pathnames } from "./config/lang-config";
    import withAuth from 'next-auth/middleware';
    import { NextRequest, NextResponse } from 'next/server';
    import { getProfile } from './services/authQueries';
    
    const privatePages = [
        '/profile',
        '/settings',
        '/settings/*',
        '/wallet',
        '/wallet/*',
        '/auth/logout',
    ];
    
    const intlMiddleware = createMiddleware({
        defaultLocale,
        locales,
        localePrefix: "as-needed",
        pathnames,
        localeDetection: true,
    });
    
    const authMiddleware = withAuth(
        function onSuccess(req) {
            return intlMiddleware(req);
        },
        {
            callbacks: {
                authorized: async ({ token }: { token: any }) => {
                    const accessToken = token?.tokenData?.token || token?.token as string;
                    if (token) {
                        try {
                            const res = await getProfile(accessToken, 'en');
                            if (res.isSucceed) {
                                token.user = res.data;
                                return true;
                            } else {
                                throw new Error(res.message);
                            }
                        } catch (error) {
                            console.error(error instanceof Error ? error.message : 'An unknown error occurred');
                            return false;
                        }
                    }
                    return false;
                },
            },
            pages: {
                signIn: '/',
            }
        }
    );
    
    const env = process.env.NODE_ENV;
    const blockedCountries = ['US', 'UK'];
    
    export default async function middleware(req: NextRequest) {
        // Blocked user check
        const country = req.geo?.country || req.headers.get('cloudfront-viewer-country') || req.headers.get('x-vercel-ip-country');
        if (env !== 'development' && blockedCountries.includes(country ?? '')) {
            return NextResponse.redirect(new URL('/blocked', req.url));
        }
    
        const pathname = req.nextUrl.pathname;
        const privatePathnameRegex = new RegExp(
            `^(/(${locales.join('|')}))?(${privatePages
                .map((p) => p.replace(/\*/g, '.*'))
                .join('|')})/?$`,
            'i'
        );
    
        // Apply auth middleware on private pages
        if (privatePathnameRegex.test(pathname)) {
            return authMiddleware(req);
        }
    
        // Apply intlMiddleware on public pages and set country cookie
        const response = intlMiddleware(req);
        response.cookies.set("client-country", country || '');
        return response;
    }
    
    export const config = {
        matcher: [
            '/',
            '/(en-US)/:path*',
            '/((?!api|_next|_vercel|.*\\..*).*)'
        ]
    };
    

    Blocked User Check: Placed at the very beginning to ensure redirection for blocked countries before intlMiddleware runs.

    Private Page Auth Check: We match private pages with authMiddleware while leaving other routes to intlMiddleware to handle localization. Consistent Locale Handling: By structuring the middleware like this, intlMiddleware always runs on non-private pages, avoiding the locale detection error.

    This setup should redirect blocked users to /blocked, handle authentication on private pages, and apply consistent locale detection and response processing on public pages.