Skip to content
SEO Evaluate
Related posts
SEO5 min read

Next.js technical SEO: proxy, canonical, locale

Next.js 16 replaced middleware with proxy. The traps in proxy, canonical and locale setup on multilingual Next.js sites.

By Roozbeh Nazari · CEO

Next.js technical SEO: proxy, canonical, locale

On multilingual sites built with Next.js, most technical SEO faults occur not on individual pages but in the layer a request passes through before it reaches the page. That layer was known as middleware for a long time. With Next.js 16 the file convention was deprecated and renamed to proxy; middleware.ts became proxy.ts, and the exported function is now named proxy. The stated reason for the rename is that the term "middleware" was often confused with Express.js middleware, which encouraged wider use of the feature than intended.

From an SEO standpoint this is not merely a rename. On teams that upgrade without updating their setup, the locale and canonical arrangement can quietly stop working at a point nobody notices. The points below cover the faults this layer most often produces in multilingual Next.js setups. For the rest of the audit, see our technical SEO service and our 20-item production audit checklist.

The layer that disappears in the migration

In a project upgraded to Next.js 16, if middleware.ts is left in place the file convention is no longer recognised. The result is not an error message but a silent change in behaviour: the locale redirects, the language-cookie rewrites and the header additions defined in that file simply stop running. The site keeps serving, pages return 200, no test breaks. Only the language arrangement is gone.

Next.js provides a codemod for the migration; it renames the file and the function together. For teams upgrading, the correct order is to run the codemod and then verify locale behaviour by hand in an environment resembling production. Verification should be done with requests whose Accept-Language header is set manually, not in a browser; the browser sends its own language anyway and can mask the problem.

Matcher: the most expensive three lines

With no matcher defined, proxy runs for every request. That includes static files, image optimisation paths and assets in the public folder. The typical fault in multilingual setups is a rewrite that adds a language prefix also catching static assets; the result is stylesheets and scripts failing to load, or being redirected to unexpected paths.

  • When writing a negative match pattern, the sitemap.xml and robots.txt paths must be excluded. A sitemap address that has picked up a language prefix is not the address the search engine expects.
  • API paths should be excluded; an API address with a language prefix added will not work.
  • Matcher values have to be constants. A matcher produced from a variable cannot be analysed at build time and is silently ignored; the setup is assumed to be working.

A further subtlety: even when excluded in a negative match pattern, proxy is still invoked for data routes. This behaviour is intentional, and exists to prevent the mistake of protecting a page while leaving its corresponding data route unprotected. In a language setup that means data requests also pass through the same rewrite logic, and that logic must not corrupt the data route address.

Canonical: in metadata, not in proxy

A common error in multilingual setups is adding the canonical address as a header in the proxy layer. That layer runs before the page is rendered and does not always know which content the page will show; the canonical it produces may not match the post-rewrite address. The right place is the metadata object defined at page or layout level.

On the metadata side, canonical and language alternates are defined under the alternates field: canonical carries the page’s own preferred address, and languages carries the addresses of the language versions. On dynamic routes these values need to be produced inside generateMetadata; a canonical defined with a static metadata object points every dynamic page at the same address, producing the classic template fault.

The rule on Google’s side is independent of this: canonical is a strong signal, not a directive. Redirects are the strongest deduplication method, with the canonical tag second. So when you produce conflicting signals — for instance a canonical pointing at one address while the language declaration points at another — you are not the one who decides the outcome.

Locale: the auto-redirect trap

The most tempting use of the proxy layer is redirecting a visitor to a language version based on their browser language. That arrangement looks sensible in user-experience terms and is expensive in SEO terms. The search engine crawler arrives with a single language preference; once a forced redirect is in place, the other language versions are never crawled and the members of the language group are not discovered.

We covered that trap in detail in our piece on locale auto-redirect. The Next.js-specific point to add is this: when the redirect logic is written inside proxy, every request — including asset requests, depending on the matcher — passes through it and redirect chains form. The length of the chain grows with the number of language versions.

The safe arrangement is to suggest the language rather than force it: let the visitor stay on the version they are on and offer a visible option for their preferred language. That approach keeps every language version crawlable at its own address and does not take the choice away from the user.

Runtime and verification

Proxy uses the Node.js runtime by default, and the runtime configuration option cannot be set inside the file; attempting to set it throws an error. Runtime declarations carried over from older setups therefore need clearing out during the upgrade.

On verification, the test utilities introduced in Next.js 15.1 allow you to assert with unit tests which addresses the proxy file runs on. In multilingual setups the payoff is concrete: a handful of tests asserting that sitemap, robots and asset paths fall outside proxy coverage while language-prefixed paths fall inside it will catch, at build time, faults that take weeks to notice in production.

The practical rule for this layer as a whole: proxy should be used for redirects and rewrites, not for the signals that establish a page’s identity. Canonical, language declarations and title tags belong to the page layer. When the two get mixed, the resulting fault is not on one page but across the whole template, and it typically scales with the page count.

Post-upgrade checklist

On a multilingual project moving to Next.js 16, there is a short list to run by hand once the upgrade is done. It does not replace automated tests; it exists to catch the "working but working wrongly" class of fault that automated tests do not cover.

  • Is there a proxy.ts at the root, and is the exported function named proxy? A file left under the old name is silently ignored.
  • With the Accept-Language header set manually, does each language version return 200 at its own address, or does everything redirect to one version?
  • Are sitemap.xml and robots.txt reachable without picking up a language prefix?
  • On a dynamic page, does canonical point to that page’s own address, or to a single address across the whole template?
  • Are the language declarations reciprocal, and do they point at the same address as canonical?

Sources

// CONTACT

Drop a brief. Send us your brief.

Our intro call is free. Once we have your brief, we'll map the market opportunity and your highest-priority growth opportunities.