Cloudflare language based redirection
06:47 25 Nov 2025

I am trying to create a language redirection system with the following behavior.

If the user visits the website for the first time it should go to their browserlanguage and country in my case browserlang is en and country is nl so (/en-nl/)

When the user changes their language it sets a cookie called preffered-language. (value could be "nl-nl")

Now this is a hubspot website so I make a call to check if the page is avaible it languages that is what i currently have setup.

I get this back if i would go to

https://language-redirect.bent.workers.dev/en-nl/why-test/easy-to-use

{
  "id": "298225991928",
  "slug": "en-nl/why-test/easy-to-use",
  "translations": {
    "nl-nl": {
      "id": 298228102349,
      "slug": "nl-nl/why-test/easy-to-use",
    }
  }
}

This is build with the following code:

export default {
  async fetch(request, env, event) {
    return handleRequest(request, env, event)
  }
}

// --- Fetch all pages with pagination ---
async function fetchAllPages(url, token, extraParams = {}) {
  let results = []
  let after = undefined

  do {
    const fullUrl = new URL(url)
    fullUrl.searchParams.set('limit', '100')
    if (after) fullUrl.searchParams.set('after', after)

    for (const key in extraParams) {
      fullUrl.searchParams.set(key, extraParams[key])
    }

    const resp = await fetch(fullUrl.toString(), {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    })

    if (!resp.ok) {
      const txt = await resp.text()
      throw new Error(`HubSpot API error on ${url}: ${resp.status} — ${txt}`)
    }

    const json = await resp.json()
    const minimal = json.results.map(p => ({
      id: p.id,
      slug: p.slug,
      translations: p.translations || {},
      type: p.type || 'site-page' // remember type for variant fetch
    }))
    results = results.concat(minimal)
    after = json.paging?.next?.after
  } while (after)

  return results
}

// --- Normalize URL paths ---
function normalizePath(p) {
  return p.replace(/^\/+|\/+$/g, '')
}

// --- Extract language code from URL ---
function extractLanguage(path) {
  const match = path.match(/^\/([a-z]{2}-[a-z]{2})\//i)
  return match ? match[1].toLowerCase() : null
}

// --- Main handler ---
async function handleRequest(request, env, event) {
  const HUBSPOT_TOKEN = env.HUBSPOT_TOKEN
  if (!HUBSPOT_TOKEN) return new Response("HUBSPOT_TOKEN not set", { status: 500 })

  const cache = caches.default
  const cached = await cache.match(request.url)
  if (cached) return cached

  try {
    const requestUrl = new URL(request.url)

    // --- Parse cookies safely ---
    const cookies = {}
    ;(request.headers.get('Cookie') || '').split(';').forEach(cookie => {
      const [name, ...rest] = cookie.trim().split('=')
      cookies[name] = rest.join('=')
    })
    const preferredLang = cookies['preferred_language']

    // --- Detect browser language ---
    const acceptLang = request.headers.get('Accept-Language') || 'en-US'
    let browserLang = acceptLang.split(',')[0].toLowerCase()
    if (!browserLang.includes('-')) {
      const country = (request.headers.get('CF-IPCountry') || 'US').toLowerCase()
      browserLang = `${browserLang}-${country}`
    }

    const requestedLang = extractLanguage(requestUrl.pathname) || browserLang
    const fullSlug = normalizePath(requestUrl.pathname)

    // --- Fetch all pages ---
    const sitePagesUrl = 'https://api.hubapi.com/cms/v3/pages/site-pages'
    const landingPagesUrl = 'https://api.hubapi.com/cms/v3/pages/landing-pages'

    const [sitePages, landingPages] = await Promise.all([
      fetchAllPages(sitePagesUrl, HUBSPOT_TOKEN, { draft: true }),
      fetchAllPages(landingPagesUrl, HUBSPOT_TOKEN, { draft: true })
    ])
    const allPages = sitePages.concat(landingPages)

    // --- Match current page ---
    let matchedPage = allPages.find(p => normalizePath(p.slug || '') === fullSlug)

    // Fallback: match default page without language prefix
    if (!matchedPage) {
      const pathWithoutLang = fullSlug.replace(/^[a-z]{2}-[a-z]{2}\//, '')
      const defaultPage = allPages.find(p => normalizePath(p.slug || '') === pathWithoutLang)

      if (defaultPage && defaultPage.translations?.[requestedLang]) {
        const variantId = defaultPage.translations[requestedLang].id
        const variantType = defaultPage.type === 'landing-page' ? 'landing-pages' : 'site-pages'
        const variantResp = await fetch(
          `https://api.hubapi.com/cms/v3/pages/${variantType}/${variantId}`,
          { headers: { Authorization: `Bearer ${HUBSPOT_TOKEN}` } }
        )
        if (!variantResp.ok) throw new Error(`HubSpot API error fetching variant: ${variantResp.status}`)
        matchedPage = await variantResp.json()
      }
    }

    if (!matchedPage) {
      return new Response(JSON.stringify({ error: "Page not found" }), {
        status: 404,
        headers: { 'Content-Type': 'application/json' },
      })
    }

    // --- Automatic language redirect ---
    const targetLang = preferredLang || browserLang
    const translation = matchedPage.translations?.[targetLang]

    if (!preferredLang && translation) {
      const currentSlug = normalizePath(requestUrl.pathname)
      const targetSlug = normalizePath(translation.slug)

      if (currentSlug !== targetSlug) {
        // Build full absolute URL for redirect
        const redirectUrl = new URL(requestUrl)
        redirectUrl.pathname = `/${targetSlug}`
        return Response.redirect(redirectUrl.toString(), 302)
      }
    }

    // --- Serve JSON if no redirect ---
    const response = new Response(JSON.stringify(matchedPage, null, 2), {
      headers: { 
        'Content-Type': 'application/json',
        'Cache-Control': 'public, max-age=86400, stale-while-revalidate=3600'
      },
    })
    event.waitUntil(cache.put(request.url, response.clone()))
    return response

  } catch (err) {
    return new Response(`Error: ${err.message}`, { status: 500 })
  }
}

Now Is it possible to create this using cloudflare and what would be the downsides? What about limitations/usage payments?

Please let me know if this explains what i am trying to achieve I am new to cloudflare.

cloudflare cloudflare-workers hubspot