Furkan Tombaş
Furkan Tombaş

Reputation: 13

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."

Upvotes: 1

Views: 118

Answers (1)

Ashman Malik
Ashman Malik

Reputation: 16

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.

Upvotes: 0

Related Questions