Serving Webpack assets from a different domain
By: Jake Ginnivan
Tags: Webpack, S3, Cdn, AWS

Our news sites are React server side rendered websites, with a CDN in front of them. This means that 95%+ of our traffic is served cached copies of pages without ever hitting our servers (origin). It gives us most of the advantages of static site generators without the complexity in our build pipeline.

We recently wanted to move our static assets (fonts, JS bundle/chunks, css etc) to a subdomain rather than a path.

If you just want to know how to configure Webpack and React Loadable to load assets from a different domain, scroll to the bottom of this post.

Why?

Not all CDN products are the same, often they come with WAF (web application firewall), DDoS protections and more advanced features to improve the delivery speed of your website to clients.

These features cost money, and are not really needed for static assets. By moving our static files to a different CDN we could take advantage of cheaper CDN rates because they did not have all these features and various other reasons. Across millions of page views, a 20% reduction in CDN rates can add up to a fair reduction.

Background

CDNs (content distribution networks) enable your website (the Origin Server) to be served from an Edge Server close to the user. The CDN network takes care of caching and serving your website from a server close to the user, making it far faster and reducing traffic/compute on your origin server.

CDN map

Existing setup

We rolled our own tool many years ago before Razzle, Create React App and other similar projects existed around called Project Watchtower. It gives us a nice development experience and builds our sites into a shippable dist/ folder, ready to deploy as well as our Server Side Rendering middlewares.

By default our assets are build into a static folder.

Static assets from website build

Everything inside this folder is cache busted, so before our website deploys all these assets are uploaded into S3. Our CDN is then configured that the static/ path has an origin of S3 (CDNs have two sides, the Edge where traffic is served from, and the origin, where the CDN gets the original content from)

CDN configured /static path to have a different origin

There is a good blog post by AWS on using CloudFront (AWS’s CDN product) backed by S3. The great thing about CDNs is that you can configure rules like this reasonably easily.

New setup

We setup a new subdomain for each of our News sites, so https://s.thewest.com.au for https://www.thewest.com.au. Then rather than all assets being fetched from the static/ sub-path, they are fetched from a completely different domain.

In essence https://www.thewest.com.au/static/js/main.a4bs5.js moves to https://s.thewest.com.au/static/js/main.a4bs5.js.

How?

Webpack has a build configuration setting called publicPath , we have this set to /static so all assets are built into a consistent subfolder. Our goal for all of our projects is to build once, then deploy to multiple environments so we could not use publicPath to set this up.

Luckily for us, Webpack allows you to change the public path dynamically at runtime using the __webpack_public_path__ variable.

On the server

This variable must be set BEFORE your imports/requires run. If using ES6 imports they will always end up at the top of your file so the best bet is to set it in a separate module

Ensure this is the FIRST import in your server entry. This will take care of anytime you require('./image.png') or similar in your server application.

Next you need to make sure React Loadable can resolve any lazy loaded chunks from the correct public path. The ChunkExtractor which based on what components are rendered allows you to include the script tags for those bundle split files in the server side rendered response has the ability to specify the publicPath at runtime.

With these two things in place, our server side rendering will now reference absolute URLs by setting the PUBLIC_PATH environmental variable. Notice the <script>window.PUBLIC_PATH = '${process.env.PUBLIC_PATH}'</script> in the above, we will use that in the next part

On the client

Now on the client we need to create the module which sets the webpack path.

NOTE: __webpack_public_path__ is not a normal global variable, if you don’t set it inside your bundle it will not take effect. My original attempt server side rendered the assignment of that variable but it is not picked up. So we need to take the same approach as the server, create a module then ensure it is the first import in your client entry.

Summary

CDNs are a must for any public facing site with decent traffic and the ability to quickly move static assets to another domain to take advantage of better CDN rates due for any reason is pretty great.