We have a Next.js site running on Netlify. The production URL is https://www.metrie.com/, however this issue affects all our environments (dev, staging, and production) as well as deploy previews.
We recently upgraded Next.js to 12.3, which required us to rewrite our middleware from the older beta standard to the final stable version.
Since that upgrade, I’ve been seeing odd behavior on just a few specific URLs, for example: Aperçu des produits | Metrie
The site currently supports 3 locales (EN, FR, ES) and uses Middleware to localize the routes for French and Spanish. The actual defined Next.js route is /products
, but we use middleware to rewrite (not redirect) /fr/produits
and /es/productos
to that route.
The relevant portion of the Middleware file looks like this:
if (matchedRoute) {
const source = matchedRoute.source.replace(':slug*', '*');
const p = new URLPattern({ pathname: source });
const routePattern = p.exec(href);
let redirectUrl = `${protocol}//${host}/${locale}${matchedRoute.destination}`;
let slug = '';
if (routePattern?.pathname.groups?.[0]) {
slug = routePattern.pathname.groups?.[0];
}
redirectUrl = redirectUrl.replace(':slug*', slug);
return NextResponse.rewrite(new URL(redirectUrl));
}
This works, and Aperçu des produits | Metrie is fully accessible on the site, both by directly entering the URL and through the Next.js client-side routing. However, if you type the URL in directly for just this URL and a couple of its child routes (for example, Solutions de produits | Metrie), Netlify is serving a 301 redirect to the English route.
Here’s an example of the response to GET /fr/produits:
age: 40441
cache-control: public, max-age=0, must-revalidate
content-encoding: gzip
content-type: text/html; charset=UTF-8
date: Mon, 02 Jan 2023 05:32:03 GMT
etag: W/"4167ea6dfb5bacbb4a8b99d0578f558c-ssl-df"
location: /fr/products
server: Netlify
strict-transport-security: max-age=31536000
vary: Accept-Encoding
x-middleware-rewrite: /fr/products/
x-nf-request-id: 01GNSP0E8MYH7KQTSP9VGJ86H7
Which is then followed immediately by a GET for /fr/products (response follows):
age: 40440
cache-control: public, max-age=0, must-revalidate
content-encoding: gzip
content-type: text/html; charset=UTF-8
date: Mon, 02 Jan 2023 05:32:04 GMT
etag: W/"4167ea6dfb5bacbb4a8b99d0578f558c-ssl-df"
server: Netlify
strict-transport-security: max-age=31536000
vary: Accept-Encoding
x-middleware-next: 1
x-nf-request-id: 01GNSP0EAX2Y6DEVP5SNZE6W6H
The content is served correctly at this URL, but it’s producing a bad-SEO situation where identical content is served from two different URLs.
According to the Next.js documentation, the Middleware rewrite()
method should transparently proxy the URL and should not produce a 301 redirect or change the URL, so I’m having trouble figuring out where this is coming from.
I’ve already checked the _redirects
file (which is quite large on this site) as well as the generated netlify.toml
file and verified that there is no server-side redirect from /fr/produits -> /fr/products
. I’ve also tested it with and without trailing slashes and the behavior is identical.
Finally, it’s interesting to note that other URLs that are rewritten by middleware don’t display the same issue. For example, /fr/où-acheter
never visibly redirects to /fr/where-to-buy
, even though that is the actual Next.js route.
Thanks in advance for any help!