Optimised

Tips, insights, and articles to help boost your website's performance.

Issue #22

August 6, 2021

Proxying third-party requests

I'm writing the newsletter this week in the breaks between Olympic events. Both my home country's (Australia & Taiwan) have had decent medal hauls - especially Taiwan, which is a country where sports is given nowhere near the importance it deserves in childhood & teenage development.

This issue of Optimised will be a bit more technical than the last few. In it I'll be showing you a couple of ways you can proxy third-party requests on your site, and why that can help with performance. Let's get into it.


I've written about the perils of relying on third-party resources previously (see Issue 2 - Third-party resources - A cautionary tale). That said, most of the time you'll be unable to avoid using at least some third-party hosted assets on your website. Whether it's an analytics provider, image hosting, advertising, or even a cookie consent manager.

There are a few ways third-party resources can impact on site performance. But there is one that is common to any and all third-party resources. That is, that for each third-party domain you use the browser must open up a new HTTP connection, then go about resolving the DNS, before finally opening a TCP connection and performing SSL negotiation. This can add up-to (sometimes over) a second to a request. In a world where milliseconds makes millions, that's a hefty delay.

One way to get around this is to host resources on your own domain. Barring that, another workaround is to shift the work off the browser, and onto your host/CDN. Since hosts/CDNs process millions of requests a minute they're better optimised to resolve DNS fast, which can shave several hundred milliseconds of request times. By setting up a proxy, you can trick the browser into believing a resources is being requested from your own domain, when in fact it is coming from a third-party.

Below are a couple of examples, both using Cloudflare Workers, that show you some ways to proxy third-party requests. You can set up something similar on most CDNs that provide edge-compute, or even on your web host.

Proxying Cloudinary image requests with Cloudflare Workers

Over on my personal website, I use Cloudinary to host and serve my images. Normally, this would mean that for the first image requested on a page would incur the DNS-TCP-SSL penalty I mentioned earlier. Instead, I can use a Cloudflare Page Rule that routes all traffic from the fershad.com/image path to a Cloudflare Worker. The Worker intercepts the request, and fetches the requested asset from Cloudinary. As far as the browser is concerned, it's requesting and receiving a file from my domain.

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event));
});

const CLOUD_URL = `https://res.cloudinary.com/${CLOUD_NAME}`;

async function serveAsset(event) {
  const url = new URL(event.request.url);
  const cloudinaryURL = `${CLOUD_URL}${url.pathname}`;
  response = await fetch(cloudinaryURL);
  const headers = {
    "timing-allow-origin": "*",
  };
  response = new Response(response.body, { ...response, headers });
  return response;
}

async function handleRequest(event) {
  if (event.request.method === "GET") {
    let response = await serveAsset(event);
    if (response.status > 399) {
      response = new Response(response.statusText, { status: response.status });
    }
    return response;
  }
  return new Response("Method not allowed", { status: 405 });
}

The script I use in production includes a couple of extra steps to cache the returned images, so that subsequent requests for the same file are served using the cache. You can read more about that in this guide from Cloudflare. If you're using Netlify to host your site, Tim Kadlec has a tutorial showing how to do the same thing with Netlify redirects.

Proxying AWS S3 content with Cloudflare Workers

Another common use case for proxying requests would be when hosting content in Amazon Web Services S3 buckets. Not only is this handy for performance, but it can also reduce your AWS bill if you cache assets that are delivered by the proxy. Another benefit is that it allows you to use international S3 buckets to serve content to users that might be located in China - which has special conditions in place for storing & delivering AWS content.

Again, this works with a Cloudflare Page Rule that sends all traffic on the /files route through the worker below.

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event));
});

async function handleRequest(event) {
  let requestFolder = "/files";
  let s3Folder = "/uploads";

  let url = new URL(event.request.url);
  const cache = caches.default;

  let origPathname = url.pathname;
  const filename = url.toString().split("/").pop();

  url.hostname = "[YOUR_S3_BUCKET_URL_HERE]";
  url.pathname = origPathname.replace(
    new RegExp("^" + escapeRegExp(requestFolder)),
    s3Folder
  );

  response = await fetch(url);
  response = new Response(response.body, { ...response });

  return response;
}

function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\\/]/g, "\\$&");
}

Resources

Articles

The performance effects of too much lazy-loading
Lazy loading images is a helpful strategy to improve speed. Being too aggressive with it, however, can have negative effects on performance. This article from the team at Google goes into how they uncovered some of these impacts and how to address them, especially when using site builders like WordPress.


The next issue of Optimised will be in your inbox on August 20th. The website has an archive of all previous emails. It's a good place to recap on anything we've covered, and also handy to share with friends or colleagues.

As always if you've got any feedback or specific topics you want to be covered then just reply to this email.

Keep safe, stay well.
Fershad.

Subscribe

Receive fortnightly website optimisation tips, insights, and articles directly to your inbox.
Alternately, you can receive updates using RSS.

This newsletter is powered by Buttondown.