WSS
BlogInternationalisationArchitecturePerformancePublished

Zero-Latency i18n: Language Negotiation at the Edge

Auto-redirecting users based on Geo-IP is a massive anti-pattern that breaks crawlers, ruins caching, and adds latency. Here is how to handle Accept-Language headers and route users at the CDN edge without destroying your Core Web Vitals.

If you operate a global website, you eventually face the Internationalisation (i18n) Routing Problem: When a user visits your root domain https://yoursite.com/, which language do you show them?

For years, the industry standard was a naive, destructive anti-pattern: The Geo-IP Auto-Redirect.

A user in Berlin types in yoursite.com. The DNS resolves, the TCP connection is established, the TLS handshake completes. The origin server looks up their IP address in a Geo-IP database, determines they are in Germany, and sends a 302 Found redirect to https://yoursite.com/de/. The browser then performs another full round trip to fetch the German content.

This approach is fundamentally flawed for three reasons:

  1. It adds massive latency. A full HTTP round trip is wasted just to tell the browser to go somewhere else. This destroys your Time to First Byte (TTFB) and First Contentful Paint (FCP).
  2. It assumes Geography equals Language. An American expat living in Berlin does not want your German site. An English-speaking bot crawling from an AWS data center in Frankfurt does not want your German site.
  3. It ruins caching. If yoursite.com/ redirects based on IP, it cannot be safely cached by a standard CDN without complex Vary headers that quickly fragment the cache hit rate to zero.

The solution is not to run this logic on your origin server. The solution is to negotiate language at the Edge, using the browser’s own stated preferences, without ever issuing an HTTP redirect.

The Accept-Language Header

Every HTTP request sent by a modern browser includes an Accept-Language header.

Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

This header is the user explicitly telling you: “I prefer Swiss French. If you don’t have that, I’ll take any French. If you don’t have French, I prefer English over German.”

This is vastly more accurate than a Geo-IP lookup.

However, reading this header on your origin server still means the request had to travel all the way to your origin. If your server is in Virginia and the user is in Tokyo, you’ve just added 200ms of latency before you even start parsing the header.

Edge Routing: The Modern Approach

Modern Edge Compute platforms (Cloudflare Workers, Vercel Edge Middleware, Fastly Compute@Edge) allow you to run lightweight JavaScript directly on the CDN node closest to the user, typically within 10-20 milliseconds.

We can use the Edge to intercept the request to /, parse the Accept-Language header, and transparently fetch the correct language variant from the origin—or better yet, from the Edge cache—without issuing a redirect.

The Edge Middleware Implementation

Here is how you handle i18n routing at the edge (example using standard Web Request/Response APIs):

const SUPPORTED_LOCALES = ['en', 'fr', 'de', 'ja'];
const DEFAULT_LOCALE = 'en';

export async function onRequest(context) {
  const { request } = context;
  const url = new URL(request.url);

  // 1. Only intercept requests to the root path
  if (url.pathname !== '/') {
    return context.next();
  }

  // 2. Check for an explicit user override cookie
  const cookie = request.headers.get('Cookie') || '';
  const match = cookie.match(/preferred_locale=(?P<locale>[a-z]{2})/);
  let targetLocale = match?.groups?.locale;

  // 3. Fallback to Accept-Language parsing
  if (!targetLocale || !SUPPORTED_LOCALES.includes(targetLocale)) {
    const acceptLanguage = request.headers.get('Accept-Language') || '';
    targetLocale = negotiateLanguage(acceptLanguage, SUPPORTED_LOCALES) || DEFAULT_LOCALE;
  }

  // 4. Transparently rewrite the URL and fetch from the origin/cache
  url.pathname = `/${targetLocale}/`;
  
  const response = await fetch(url, request);

  // 5. Append the Vary header so downstream caches respect the Accept-Language
  const modifiedResponse = new Response(response.body, response);
  modifiedResponse.headers.append('Vary', 'Accept-Language, Cookie');
  
  return modifiedResponse;
}

// A simple language negotiator
function negotiateLanguage(header, supported) {
  const requested = header.split(',')
    .map(lang => lang.split(';')[0].trim().substring(0, 2).toLowerCase());
    
  for (const lang of requested) {
    if (supported.includes(lang)) return lang;
  }
  return null;
}

The Architectural Benefits

By transparently rewriting the request at the edge:

  1. Zero Redirect Latency: The user in Tokyo asks for /. The Tokyo edge node instantly sees they want Japanese (ja), fetches /ja/ from its local cache, and serves it back. The user experiences sub-50ms TTFB.
  2. Perfect SEO: Search engine crawlers hitting / will receive the default locale (usually English), but your <link rel="alternate" hreflang="..."> tags will point them to the explicit /fr/ and /de/ URLs. Because there is no Geo-IP redirect, Googlebot (crawling from the US) can successfully index your European sites without being forcibly redirected away.
  3. User Choice: We prioritize the preferred_locale cookie. If an expat manually switches the site to English, we store that cookie. The Edge reads it instantly and serves English, regardless of their IP or browser settings.

The Explicit URL Rule

Notice in the architecture above that the Edge transparently maps / to /${targetLocale}/.

It is a critical rule of web architecture that explicit language URLs must never be redirected.

If a user explicitly requests yoursite.com/fr/about/, you must never parse their Accept-Language or Geo-IP to redirect them. If they requested the French URL, they want the French URL. If a user shares a link to the German version of a document, anyone clicking that link must see the German version.

Language negotiation should strictly apply only to language-agnostic root paths (like /). Once a user is in a localized path hierarchy (/fr/...), the URL is the absolute source of truth.

Conclusion

The Geo-IP redirect is a relic of an era before pervasive Edge computing. Today, forcing a user through a 302 redirect based on a flawed assumption about their geography is a failure of architecture.

By leveraging the Accept-Language header and Edge Middleware, you can deliver localized content with zero additional latency, perfect SEO compatibility, and respect for user preference.

Related posts