Redirects in middleware.js are not executing on Production

I have a critical redirect issue on my project biomanage-next (domain: biomanage.it) that I cannot solve. My site uses an A-Record setup (not Vercel nameservers).

My Problem: Google is reporting redirect errors for www.biomanage.it and URLs with trailing slashes (like /en/). My “Test URL Live” in Google Search Console confirms that these redirects are not working (they return a 200 OK).

What I Did (The Final Solution): I have put all redirect logic into my middleware.js file, as this is the only file that should execute on an A-Record setup. I have removed all redirect logic from next.config.js and vercel.json to prevent conflicts.

The Problem: I have deployed this code (and cleared the cache on Vercel), but Google’s “Test URL Live” (and my own tests) show that this middleware is not running. Requests to https://www.biomanage.it/ and https://biomanage.it/en/ still return a 200 OK, not a 308 redirect.

It seems like my middleware.js file is being ignored or not deployed correctly, even though the deploy log says it’s complete.

Can you please investigate why my middleware redirects are not executing?

Here is my complete middleware.js file:

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

let locales = \[‘en’, ‘it’\];
let defaultLocale = ‘it’;

function getLocale(request) {
const acceptLanguageHeader = request.headers.get(‘accept-language’);
const languages = acceptLanguageHeader ? new Negotiator({ headers: { ‘accept-language’: acceptLanguageHeader } }).languages() : [ ];
return match(languages, locales, defaultLocale);
}

export function middleware(request) {
const { pathname } = request.nextUrl;
const hostname = request.nextUrl.hostname;

if (
pathname.startsWith(‘/\_next/’) || pathname.startsWith(‘/api/’) ||
pathname.startsWith(‘/photo/’) || pathname.startsWith(‘/flags/’) ||
pathname.startsWith(‘/font/’) || pathname.includes(‘.’)
) {
return NextResponse.next();
}

// 2. Redirect www to non-www
if (hostname.startsWith(‘www.’)) {
const newUrl = request.nextUrl.clone();
newUrl.hostname = hostname.replace(‘www.’, ‘’);
return NextResponse.redirect(newUrl, 308);
}

// 3. Remove trailing slash
if (pathname.endsWith(‘/’) && pathname.length > 1) {
const newUrl = request.nextUrl.clone();
newUrl.pathname = pathname.slice(0, -1);
return NextResponse.redirect(newUrl, 308);
}

// 4. Language logic
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);

if (pathnameHasLocale) {
return NextResponse.next();
}

const locale = getLocale(request);
const url = request.nextUrl.clone();
url.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(url);
}

export const config = {
matcher: \[
‘/((?!api|\_next|.+\\..+).\*)’,
\],
};

Hi @parminico, welcome to the Vercel Community!

If I understand correctly you want to redirect the URLs with trailingSlash to the URLs without one.

> curl -IL https://www.biomanage.it/en/
HTTP/2 301 
cache-control: public, max-age=0, must-revalidate
content-type: text/plain
date: Mon, 03 Nov 2025 11:36:46 GMT
location: https://biomanage.it/en/
server: Vercel
strict-transport-security: max-age=63072000
x-vercel-id: bom1::xnc2m-1762169806203-6ef888d34e0d

HTTP/2 308 
cache-control: public, max-age=0, must-revalidate
content-type: text/plain
date: Mon, 03 Nov 2025 11:36:46 GMT
location: /en
refresh: 0;url=/en
server: Vercel
strict-transport-security: max-age=63072000
x-vercel-id: bom1::287wr-1762169806317-06ee5cdc903f

HTTP/2 200 
age: 0
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
content-type: text/html; charset=utf-8
date: Mon, 03 Nov 2025 11:36:46 GMT
server: Vercel
strict-transport-security: max-age=63072000
vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch
x-matched-path: /[lang]
x-powered-by: Next.js
x-vercel-cache: MISS
x-vercel-id: bom1::iad1::4sk6h-1762169806353-e04c66b07aae

In my investigation, it seems like all the components are working correctly. Let me know if I missed something.

Also, about the logs: you are on the Hobby plan so Logs are only persisted for 1 hour. To test the middleware calls, you can run the cURL command while keeping the logs tab open and observe the request logs.