favoritest
kmerkuri  

Speed Up Your Site: Efficient Font Serving Through Cloudflare Workers and R2

In the world of web development, managing fonts effectively is crucial for fast-loading and responsive websites. With tools like Cloudflare Workers and R2 storage, we can serve fonts efficiently, ensuring they are both quickly accessible and safe from frequent loading requests. This guide will take you through how to implement and manage website fonts using Cloudflare Workers, R2 Buckets, and Terraform for deployment.

Getting Started

Before diving into the code, ensure you have a Cloudflare account with access to the Workers and R2 services. Terraform will be used to manage your infrastructure as code, so make sure you have it installed .

Step 1: Set Up R2 Bucket

The first step is to create an R2 bucket where your font files will be stored. After you create the bucket put the font files inside /fonts path. Here’s how you can define an R2 bucket using Terraform:

resource "cloudflare_r2_bucket" "example_r2_bucket" {
  account_id    = "<your account id>"
  name          = "example-bucket"
  location      = "eeur"  # Choose the appropriate location for your bucket
  storage_class = "Standard"
}

Step 2: Create a Cloudflare Worker

Next, you’ll create a Cloudflare Worker that will handle font requests. This worker will check whether the font is cached and, if not, fetch it from the R2 bucket.

Here’s the Terraform configuration for the Cloudflare Worker:

resource "cloudflare_workers_script" "fonts-content" {
  account_id = "<your account id>"
  name       = "fonts-script"
  module     = true
  content    = <<EOF
export default {
  async fetch(request, env, ctx) {
    const ALLOWED_METHODS_S3 = ["GET", "HEAD"];
    const ALLOWED_ORIGINS = ["https://www.klevimerkuri.com", "https://dev.klevimerkuri.com"];

    const cacheUrl = new URL(request.url);
    const cacheKey = new Request(cacheUrl.toString(), request);
    const cache = caches.default;
    var object = null;
    var response = null;
    const pathName = decodeURI(cacheUrl.pathname);
    const ORIGIN = request.headers.get('origin');
    var REAL_CACHE_CONTROL = 'max-age=300, public';

    // Caching Control
    try {
        env.CACHE_CONTROL && (REAL_CACHE_CONTROL = env.CACHE_CONTROL);
    } catch(e) { /* ignore */ }

    if (pathName.startsWith('/fonts/')) {
      if (ALLOWED_METHODS_S3.includes(request.method)) {
        if (startsWithArray(ALLOWED_ORIGINS, ORIGIN)) {
          response = await cache.match(cacheKey);
          if (!response) {
            switch (request.method) {
              case 'GET':
                object = await env.MY_BUCKET.get(pathName.slice(1));
                break;
              case 'HEAD':
                object = await env.MY_BUCKET.head(pathName.slice(1));
                break;
            }
            if (object === null) {
              response = new Response('Object Not Found', { status: 404, headers: { 'Cache-control': REAL_CACHE_CONTROL, 'Vary': 'Origin' }, });
              if (ORIGIN) response.headers.set('Access-Control-Allow-Origin', ORIGIN);
            } else {
              const headers = new Headers();
              object.writeHttpMetadata(headers);
              headers.set('etag', object.httpEtag);
              headers.set('Cache-control', REAL_CACHE_CONTROL);
              headers.set('Vary', 'Origin');
              if (ORIGIN) headers.set('Access-Control-Allow-Origin', ORIGIN);
              response = new Response(object.body, { headers });
            }
            ctx.waitUntil(cache.put(cacheKey, response.clone()));
            return response;
          } else {
            return response;
          }
        } else {
          return new Response('Origin Not Allowed', { status: 403, headers: { 'Cache-control': REAL_CACHE_CONTROL, 'Vary': 'Origin' }, });
        }
      } else {
        return new Response('Method Not Allowed', { 
          status: 405, 
          headers: { 'Allow': 'GET, HEAD', 'Cache-control': REAL_CACHE_CONTROL, 'Vary': 'Origin' }, 
        });
      }
    } else {
      return new Response('Path not found, only /fonts/ is served.', { 
        status: 404, 
        headers: { 'Cache-control': REAL_CACHE_CONTROL, 'Vary': 'Origin' },
      });
    }
  },
};

function startsWithArray(prefixes, tocheck) {
  if (!tocheck) return true;
  return prefixes.some(prefix => tocheck.startsWith(prefix));
}
EOF

  r2_bucket_binding {
    name        = "MY_BUCKET"
    bucket_name = cloudflare_r2_bucket.example_r2_bucket.name
  }

  plain_text_binding {
    name = "CACHE_CONTROL"
    text = "max-age=86400, public"
  }
}

Key Components of the Worker

  1. Allowed Methods and Origins: The worker only permits GET and HEAD methods and checks for allowed origins.
  2. Caching Practices: The worker attempts to fetch the font from the cache first. If it’s not present, it retrieves it from the R2 bucket and caches it for future requests.
  3. CORS Handling: Appropriate CORS headers are added to ensure proper cross-origin access.

Step 3: Define the Route

The last step is to set up a Cloudflare Workers route which binds the worker to a specific URL path where your fonts will be served.

resource "cloudflare_workers_route" "kmerkuri-fonts-route" {
  zone_id     = "<Your zone id>"
  pattern     = "www.klevimerkuri.com/fonts/*"
  script_name = cloudflare_workers_script.fonts-content.name
}

Important Note on Routing

Make sure that the route defined above is proxied through Cloudflare for the worker to execute properly. This ensures your fonts are served efficiently and securely.

Deploying the Stack

To deploy your Terraform configurations:

  1. Initialize your Terraform workspace:
   terraform init
  1. Apply the configurations:
   terraform apply

This will set up your R2 bucket, Cloudflare worker, and route all in one go.

Conclusion

By integrating Cloudflare Workers with R2 buckets for font serving, you create an effective, scalable solution that decreases loading times and improves the performance of your website. Using Terraform to describe the infrastructure allows you to maintain a clean and reproducible setup, making future updates a breeze.

Now you’re equipped to handle your website fonts efficiently while employing the power of Cloudflare’s infrastructure. Happy developing!

Leave A Comment