Other Articles

Using environment variables and Cloudflare HTML Rewriter API

Using environment variables and HTML Rewriter API to create a dynamic ..static.. site

Here’s how you can use environment variables, and the HTML Rewriter API of Cloudflare Worker sites, to generate a different static HTML page based on Cloudflare environment variables.

The Problem with sites built in plain HTML

If you have a static HTML site, sometimes you may need to dynamically change the links on your site, depending on what environment you’ve published the site to. Say you have a landing page that points to a SaSS app you’re building, then the links to the app in the development environment need to point to [https://dev.example.com], but in production they would need to point to [https://prod.example.com] or whatever.

Cloudflare Workers Environment Variables and HTML Rewriter to the rescue

Luckily, Cloudflare Workers has Environment Variables and the HTML Rewriter API to handle this problem for you.

Setting the Environment Variables

First, set the environment variable properly

In my wrangler.toml I added the folllowing lines, for local dev, Staging, and Production environments.

[vars]
# Normally point the app to local environment
SASS_APP_URL = 'http://localhost:3000/'

[env.development]
route = 'https://dev.example.com/*'

[env.development.vars]
SASS_APP_URL = 'https://devapp.example.com/'

[env.production]
workers_dev = false
route = 'https://example.com/*'

[env.production.vars]
SASS_APP_URL = 'https://app.example.com/'

Edit your HTML to use the Environment Variable

I edited the HTML and marked the HTML that I want to modify with an id, and variable.

<a class="main-btn rounded-one" id="signup" href="SIGNUP_VAR"/register>SIGN UP</a>
<a class="main-btn rounded-one" id="login" href="LOGIN_VAR"/login>LOGIN</a>

Edit your index.js file

Now you’ll need to edit your actual Workers index.js file that’s inside the ./workers-site directory, you tell the file to look for HTML with certain tags (in my case, signup and login), and then to replace the inner link for them.

Remember, this file is in the ./workers-site/ folder of your project.

import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'

/**
 * The DEBUG flag will do two things that help during development:
 * 1. we will skip caching on the edge, which makes it easier to
 *    debug.
 * 2. we will return an error message on exception in your Response rather
 *    than the default 404.html page.
 */
const DEBUG = false

const SIGNUP_URL = SASS_APP_URL + 'register'; 
const LOGIN_URL = SASS_APP_URL + 'login'; 

addEventListener('fetch', event => {
  event.respondWith(handleEvent(event))
})

class SignupHandler {
  element(element) {
    // An incoming element, such as `div`
    console.log(`Incoming element: ${element.tagName}`);
    const link = element.getAttribute('href');

    element.setAttribute('href',link.replace('SIGNUP_VAR', SIGNUP_URL))

  }
}

class LoginHandler {
  element(element) {
    // An incoming element, such as `div`
    console.log(`Incoming element: ${element.tagName}`);
    const link = element.getAttribute('href');

   
    element.setAttribute('href',link.replace('LOGIN_VAR', LOGIN_URL))
  }
}


async function handleEvent(event) {
  let options = {}

  try {
    if (DEBUG) {
      // customize caching
      options.cacheControl = {
        bypassCache: true,
      }
    }

    const page = await getAssetFromKV(event, options)
    
    // console.log("SIGNUP_URL IS " + SIGNUP_URL)
    // console.log("LOGIN URL IS " + LOGIN_URL)

    const response = new Response(page.body, page)

    // Look for HTML tags that are either signup, or login, and send them to 
    // their respective handlers. 

    return new HTMLRewriter()
      .on('a#signup', new SignupHandler())
      .on('a#login', new LoginHandler())
      .transform(response);


    // allow headers to be altered
    response.headers.set('X-XSS-Protection', '1; mode=block')
    response.headers.set('X-Content-Type-Options', 'nosniff')
    response.headers.set('X-Frame-Options', 'DENY')
    response.headers.set('Referrer-Policy', 'unsafe-url')
    response.headers.set('Feature-Policy', 'none')

    return response

  } catch (e) {
    // if an error is thrown try to serve the asset at 404.html
    if (!DEBUG) {
      try {
        let notFoundResponse = await getAssetFromKV(event, {
          mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
        })

        return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
      } catch (e) {}
    }

    return new Response(e.message || e.toString(), { status: 500 })
  }
}

Now, when I publish the site using wrangler, the actual URL for signup and login is generated dynamically at build time, and pushed to the correct environment.

Since this just happens when the site is being generated and published, I’m not too worried about how efficient this code is, in the end I end up with static HTML that has the correct link.