Use Case Concepts

What is language-based redirect?

A language-based redirect is an HTTP redirect that routes visitors to content in their preferred or most appropriate language, determined by analysing the visitor’s browser language settings, geographic location, or explicit language preference, rather than serving all visitors the same language version regardless of their language background. Where a regional redirect routes primarily based on geographic location a language-based redirect routes primarily based on linguistic preference, the distinction matters because geography and language do not always align perfectly.

The visitor from Brazil prefers Portuguese, not Spanish despite geographic proximity to Spanish-speaking countries. The visitor from Belgium may prefer French, Dutch, or German depending on their regional background. The visitor using a VPN exit node in Germany may actually be a French speaker located in France. Geographic routing alone handles the majority of cases correctly, but language signal detection improves routing accuracy for the significant minority of cases where geography and language diverge.

Language-based redirects are one component of a broader international web strategy, working alongside hreflang annotations, language-specific URL structures, and content localisation to serve each visitor the most appropriate language version of a site’s content. Correctly implemented language-based redirects improve user experience, visitors arrive at content in a language they understand rather than having to navigate to the correct language version, while supporting SEO by ensuring each language version reaches its intended audience.

Language detection mechanisms

Language-based redirects rely on several detection mechanisms, each with different accuracy levels, technical implementations, and appropriate use cases.

Accept-Language request header: the primary language signal available from browsers. The Accept-Language header is included in every HTTP request and communicates the user’s browser language preferences in priority order.

Accept-Language: fr-FR,fr;q=0.9,en;q=0.8,de;q=0.7

This header communicates that the visitor’s browser is configured for French, specifically French as used in France, as the primary preference, with generic French as the secondary preference, English as tertiary, and German as quaternary. Each language tag includes a quality value, q=0.9: indicating relative preference strength, values range from 0.0 to 1.0 with higher values indicating stronger preference.

Parsing the Accept-Language header enables routing to the most appropriate language variant with reasonable confidence, the browser language setting is explicitly controlled by the user and directly reflects their language preference in most cases. However Accept-Language reflects the browser’s configured language, not necessarily the user’s actual preferred language for web content. A developer who configured their browser in English but prefers French content will send an English Accept-Language despite preferring French.

IP geolocation: using the visitor’s IP address to determine their likely language based on their geographic location. Country-level geolocation maps to official or dominant languages, visitors from France are likely French speakers, visitors from Japan are likely Japanese speakers, visitors from Germany are likely German speakers.

IP geolocation is less accurate than Accept-Language for language detection, it identifies where the visitor is located rather than what language they speak. However for the majority of visitors, who are located in their home country and speak its dominant language, IP geolocation provides correct language routing. IP geolocation is particularly useful when Accept-Language signals are ambiguous or missing.

Combining both signals: the most accurate language detection combines Accept-Language and IP geolocation, using each signal to validate and supplement the other. When both signals agree, a French browser language and a French IP address, routing confidence is high. When signals disagree, an English browser language and a German IP address, the conflict can be resolved through business rules or by defaulting to the Accept-Language signal, which is the more direct language preference indicator.

A combined detection approach:

javascript

function detectLanguage(request) {
    const country = request.cf.country
    const acceptLanguage = request.headers.get('Accept-Language') || ''
    
    // Parse preferred language from Accept-Language
    const browserLanguages = acceptLanguage
        .split(',')
        .map(lang => {
            const [tag, q] = lang.trim().split(';q=')
            return { 
                tag: tag.trim().toLowerCase(), 
                quality: q ? parseFloat(q) : 1.0 
            }
        })
        .sort((a, b) => b.quality - a.quality)
    
    const primaryBrowserLang = browserLanguages[0]?.tag.split('-')[0]
    
    // Country to language mapping for disambiguation
    const countryLanguageMap = {
        'FR': 'fr', 'DE': 'de', 'JP': 'ja',
        'BR': 'pt', 'ES': 'es', 'IT': 'it',
        'CN': 'zh', 'KR': 'ko', 'RU': 'ru'
    }
    
    const countryLanguage = countryLanguageMap[country]

   // If browser and country signals agree use the browser signal
    if (primaryBrowserLang && primaryBrowserLang === countryLanguage) {
        return primaryBrowserLang
    }
    
    // If signals disagree prefer browser language — more direct indicator
    if (primaryBrowserLang && primaryBrowserLang !== 'en') {
        return primaryBrowserLang
    }
    
    // Fall back to country-based language
    return countryLanguage || 'en'
}
function detectLanguage(request) {
    const country = request.cf.country
    const acceptLanguage = request.headers.get('Accept-Language') || ''
    
    // Parse preferred language from Accept-Language
    const browserLanguages = acceptLanguage
        .split(',')
        .map(lang => {
            const [tag, q] = lang.trim().split(';q=')
            return { 
                tag: tag.trim().toLowerCase(), 
                quality: q ? parseFloat(q) : 1.0 
            }
        })
        .sort((a, b) => b.quality - a.quality)
    
    const primaryBrowserLang = browserLanguages[0]?.tag.split('-')[0]
    
    // Country to language mapping for disambiguation
    const countryLanguageMap = {
        'FR': 'fr', 'DE': 'de', 'JP': 'ja',
        'BR': 'pt', 'ES': 'es', 'IT': 'it',
        'CN': 'zh', 'KR': 'ko', 'RU': 'ru'
    }
    
    const countryLanguage = countryLanguageMap[country]

   // If browser and country signals agree use the browser signal
    if (primaryBrowserLang && primaryBrowserLang === countryLanguage) {
        return primaryBrowserLang
    }
    
    // If signals disagree prefer browser language — more direct indicator
    if (primaryBrowserLang && primaryBrowserLang !== 'en') {
        return primaryBrowserLang
    }
    
    // Fall back to country-based language
    return countryLanguage || 'en'
}
function detectLanguage(request) {
    const country = request.cf.country
    const acceptLanguage = request.headers.get('Accept-Language') || ''
    
    // Parse preferred language from Accept-Language
    const browserLanguages = acceptLanguage
        .split(',')
        .map(lang => {
            const [tag, q] = lang.trim().split(';q=')
            return { 
                tag: tag.trim().toLowerCase(), 
                quality: q ? parseFloat(q) : 1.0 
            }
        })
        .sort((a, b) => b.quality - a.quality)
    
    const primaryBrowserLang = browserLanguages[0]?.tag.split('-')[0]
    
    // Country to language mapping for disambiguation
    const countryLanguageMap = {
        'FR': 'fr', 'DE': 'de', 'JP': 'ja',
        'BR': 'pt', 'ES': 'es', 'IT': 'it',
        'CN': 'zh', 'KR': 'ko', 'RU': 'ru'
    }
    
    const countryLanguage = countryLanguageMap[country]

   // If browser and country signals agree use the browser signal
    if (primaryBrowserLang && primaryBrowserLang === countryLanguage) {
        return primaryBrowserLang
    }
    
    // If signals disagree prefer browser language — more direct indicator
    if (primaryBrowserLang && primaryBrowserLang !== 'en') {
        return primaryBrowserLang
    }
    
    // Fall back to country-based language
    return countryLanguage || 'en'
}

Explicit user selection: the most reliable language signal, the user explicitly choosing their language from a language selector. Explicit selection overrides all automatic detection, storing the selection in a cookie that persists the preference across sessions. Explicit selection is the gold standard for language routing, it eliminates all guesswork, but requires the user to take action rather than being automatically routed correctly.

Language-based redirect URL structures

The URL structure chosen for multilingual sites affects how language-based redirects are configured and how effectively search engines index each language variant.

Subdirectory structure: different language versions hosted at path prefixes under the primary domain. example.com/en/, example.com/de/, example.com/fr/. Language-based redirects from example.com route to the appropriate subdirectory based on detected language.

Subdirectory structure is generally recommended for multilingual sites, it consolidates all language versions under one domain, sharing domain authority across all language variants. Hreflang annotations and canonical tags work consistently within the single-domain structure. Language-based redirects route from the domain root, example.com: to the appropriate language subdirectory.

Subdomain structure: different language versions hosted at language-specific subdomains. en.example.com, de.example.com, fr.example.com. Language-based redirects from example.com route to the appropriate subdomain based on detected language.

Subdomain structure separates language variants more distinctly, each subdomain is treated as a somewhat independent entity by search engines while still sharing the parent domain’s authority. Subdomain-based multilingual sites are more common for larger organisations where different language teams need operational independence over their respective subdomains.

Country code top-level domain, ccTLD, structure: different language versions, or more precisely different country-targeted versions, hosted at country-specific domains. example.co.uk, example.de, example.fr. Language-based redirects from example.com route to the appropriate ccTLD based on detected country, which serves as a language proxy.

ccTLD structure provides the strongest geographic targeting signal to search engines, country-specific domains are the clearest indication of geographic targeting. However ccTLD structure requires separate domain registrations, separate DNS management, and does not consolidate authority across domains. Language-based redirects between ccTLDs and the primary domain require cross-domain redirect configuration.

SEO implications of language-based redirects

Language-based redirects have specific SEO implications that require careful management, the interaction between automatic language routing and search engine indexing can create problems if not correctly configured.

Hreflang annotations: the essential companion: hreflang annotations are the primary mechanism for communicating language and regional targeting to search engines, they are not optional for multilingual sites with language-based routing. Without hreflang Google may serve any language variant to any user, showing German speakers the English version or French speakers the German version in search results.

Every language variant page should include hreflang annotations pointing to all other language variants and to the x-default fallback:

<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="de" href="https://example.com/de/page">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/page">
<link rel="alternate" hreflang="pt-br" href="https://example.com/pt-br/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page">
<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="de" href="https://example.com/de/page">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/page">
<link rel="alternate" hreflang="pt-br" href="https://example.com/pt-br/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page">
<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="de" href="https://example.com/de/page">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/page">
<link rel="alternate" hreflang="pt-br" href="https://example.com/pt-br/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page">

The x-default hreflang tag specifies the fallback version, shown to users whose language is not explicitly targeted or when no suitable match is found.

Googlebot and language detection: Googlebot crawls from US-based infrastructure with English browser language settings. A language-based redirect that routes English browser language to the English version works correctly for Googlebot, it reaches and indexes the English content. However Googlebot cannot easily access German, French, or other language versions if those versions are only accessible through redirects that detect non-English browser languages.

Googlebot needs direct URL access to each language variant, through XML sitemap submissions and internal links, to crawl and index all language versions. Language-based redirects at the root URL level are appropriate for routing human visitors but should not be the only path to non-English content. Each language variant should have its own dedicated URL accessible directly from sitemaps and internal navigation.

Redirect type for language redirects: 302 temporary redirects are the correct choice for language-based redirects, not 301 permanent redirects. A visitor’s browser language preference is not permanent, they may browse with different devices or browser configurations that indicate different language preferences. The 302 communicates conditional routing based on a temporary signal rather than a permanent content move.

Avoiding language redirect loops: a common implementation problem. If all language variant pages redirect users whose browser language indicates a different variant to their corresponding variant every page load triggers a redirect. A German speaker visiting the French version example.com/fr/page is redirected to example.com/de/page. If the German page then redirects them back to French, because they are on the German page but somehow have a French browser signal, a redirect loop occurs.

Language-based redirects should apply only to the root or landing page level, not to every page throughout the site. Once a user is on a language variant they should remain on that variant for subsequent navigation, user preference cookies prevent re-routing on subsequent page views.

User preference persistence

Language routing that repeats itself on every visit creates a frustrating experience, users who manually navigate to a non-default language version should not be automatically redirected away from it on their next visit.

Cookie-based language preference storage: storing the detected or selected language in a first-party cookie, Accept-Language-Preference: de or language: de: persists the routing decision across sessions. The language detection logic checks for the cookie before applying dynamic detection, the stored preference overrides automatic detection:

export default {
    async fetch(request, env) {
        const url = new URL(request.url)
        const cookies = parseCookies(request.headers.get('Cookie') || '')
        
        // Check for stored language preference
        const storedLanguage = cookies['preferred_language']
        if (storedLanguage && url.pathname === '/') {
            return Response.redirect(
                `https://example.com/${storedLanguage}/`,
                302
            )
        }
        
        // Dynamic language detection for new visitors
        const detectedLanguage = detectLanguage(request)
        
        // Set cookie and redirect
        const response = Response.redirect(
            `https://example.com/${detectedLanguage}/`,
            302
        )
        response.headers.set(
            'Set-Cookie',
            `preferred_language=${detectedLanguage}; Path=/; Max-Age=31536000`
        )
        
        return response
    }
}
export default {
    async fetch(request, env) {
        const url = new URL(request.url)
        const cookies = parseCookies(request.headers.get('Cookie') || '')
        
        // Check for stored language preference
        const storedLanguage = cookies['preferred_language']
        if (storedLanguage && url.pathname === '/') {
            return Response.redirect(
                `https://example.com/${storedLanguage}/`,
                302
            )
        }
        
        // Dynamic language detection for new visitors
        const detectedLanguage = detectLanguage(request)
        
        // Set cookie and redirect
        const response = Response.redirect(
            `https://example.com/${detectedLanguage}/`,
            302
        )
        response.headers.set(
            'Set-Cookie',
            `preferred_language=${detectedLanguage}; Path=/; Max-Age=31536000`
        )
        
        return response
    }
}
export default {
    async fetch(request, env) {
        const url = new URL(request.url)
        const cookies = parseCookies(request.headers.get('Cookie') || '')
        
        // Check for stored language preference
        const storedLanguage = cookies['preferred_language']
        if (storedLanguage && url.pathname === '/') {
            return Response.redirect(
                `https://example.com/${storedLanguage}/`,
                302
            )
        }
        
        // Dynamic language detection for new visitors
        const detectedLanguage = detectLanguage(request)
        
        // Set cookie and redirect
        const response = Response.redirect(
            `https://example.com/${detectedLanguage}/`,
            302
        )
        response.headers.set(
            'Set-Cookie',
            `preferred_language=${detectedLanguage}; Path=/; Max-Age=31536000`
        )
        
        return response
    }
}

Respecting explicit user navigation: when a user explicitly navigates to a specific language version, clicking a language selector, their choice should be stored and respected. Navigation-triggered language selection updates the stored preference, subsequent automatic routing uses the explicitly selected language rather than re-running detection.

Common language-based redirect mistakes

Redirecting Googlebot to a single language: configuring language redirects that route all automated traffic, including Googlebot, to a single language variant based on browser language or IP address. Googlebot crawls with an English browser language from US infrastructure, routing it exclusively to the English version means all other language variants are inaccessible to Googlebot through the primary domain entry point.

Missing hreflang annotations: implementing language routing without hreflang annotations. Language routing ensures human visitors reach appropriate language content, hreflang annotations ensure search engines serve appropriate language content in search results. Both are required for a complete international SEO implementation.

Using 301 redirects for language routing: permanent redirect semantics are inappropriate for language-based routing, language detection is conditional on request signals that may change. 302 temporary redirects are the correct choice.

Routing all pages based on language: applying language redirect logic to every page visit rather than only entry points, causing users to be redirected whenever they navigate to any URL on the site even if they are already on the correct language variant.

No language selector UI: implementing automatic language routing without providing users any mechanism to change their language. Users for whom the automatic detection is incorrect, VPN users, travellers, multilingual users, have no way to access their preferred language version.

Related terms

Related terms

Ready to keep every link alive?

Ready to keep every link alive?

Ready to keep every link alive?