Skip to main content

Building Crazy Fast Websites with Hugo and Webpack - Part 2

Pulblished:

Updated:

Comments: counting...

In this segment we will set up Webpack to make CSS and JavaScript bundles.

Webpack

Webpack is a module bundler, we will use it to compile all of the JavaScript for a page into a single module (technically two modules, more on that later). Webpack separates the code based on how it’s used, which helps to reduce unused code and speed up page load times because the bundles are smaller, it’s also much faster to download one file than all the individual bits that would be used on a page separately. We’ll also use Babel to ensure your code will run in browser, this means we can use some newer JavaScript features that haven’t made it to the browsers yet, and Babel will transpile that code to make it compatible. Lastly, we will use Webpack for cache-busting in production builds, this way we can set all CSS/JS files to cache forever, when the file changes the filename will also change, so the browser will only download files that have changed since the last visit.

Here is the GitHub branch for this completed segment:

Install Packages

We’re going to install a lot here, so let’s run the command and you can read about it while they install:

So, we’ve got webpack of course, and its command line tool webpack-cli, webpack-dev-server we will use to proxy the Hugo site in development mode, and all the *-loader packages are used by Webpack for loading different file types. We have react and react-dom for pages that will use React. We have Babel and a bunch of plugins for it. We have Sass, Autoprefixer, and PostCSS for handling CSS files. Concurrently we’ll use to start up Webpack and Hugo at the same time. Lastly, dotenv is used to read .env files for keeping secrets safe.

How Webpack and Hugo Interact

Hugo gives each page a “Kind” and “Type”, we can use those as generic selectors for CSS and JS files. For example, if kind is “page” and type is “blog”, we can create a main.js file in src/kind/page/blog/main.js that will apply to all pages that match the same kind and type.

We can also get specific and create files such as src/path/sitemap/main.js, which would apply to the /sitemap page.

The same concepts apply for CSS files, where we can use @import to group re-usable CSS files together.

In Hugo, we can then inject those bundles into the HTML with link and script tags. In production builds, Webpack will output bundles as files into the assets/generated directory, we will check if the files exist and add them to the HTML conditionally, but in development mode there isn’t an easy way to do this because the bundles are served over the Webpack dev server (HTTP), so we’ll just add them whether they exist or not, this isn’t really an issue in development.

If this sounds confusing, don’t panic! There will be examples down below.

Folder Structure

Let’s make a directory called src in the root of the project, anything in this folder should only be consumed by Webpack. This keeps things separate from Hugo file structure. Inside of src we can make new folders for different file types like js and css, as well as two special folders called kind and path.

The kind folder has to do with Hugo page types, here we can put CSS and JS files that apply to all pages of that specific type. The path folder will mimic URL paths to apply CSS and JS files to specific pages.

In both the kind and path folders, we’ll have two types of files: main, and critical. The main CSS and JS files will be lazy loaded, and the critical CSS and JS files will be eager loaded when the page is visited. This allows fine-grained control on how each page loads its resources.

The end result will look something like this:

We’ll get more into this later, but for now let’s move assets/global.css to src/css/global.css, and assets/lazyload.js to src/js/lazyload/js.

Configure Webpack

Webpack can be pretty complex to configure, we have to hook it up to Sass, PostCSS, Babel, and other tools so it will act as a sort of hub for all of it.

First, we need to set up Babel, the .browserslistrc file tells Babel what browsers to support when transpiling code, this file also affects CSS transformation in other tools, you can learn more about this syntax in the Browserslist documentation .

A quick configuration for Babel to hook up our plugins, more information can be found in the Babel documentation .

Now on to webpack.config.js, which I’ve added plenty of comments to, again more information can be found in the Webpack documentation .

Configure Hugo Site

Now we need to get Hugo to load the Webpack bundles.

First, let’s create a partial for injecting Webpack bundles into the HTML, there is a lot going on here but it’s a pretty simple process once you wrap your head around it.

Now, in the baseof.html template, we can use the new webpack-assets partial to inject the Webpack bundles, we’ll replace the global.css link and lazyload.js script blocks here with the webpack-assets partial, and also add it to the top of <head> for critical assets.

NPM Scripts

We need to update the NPM scripts to include Webpack, we’ll add the Webpack devserver to the start script, and add a prebuild script to update Browserslist and build Webpack bundles, prebuild runs before build automatically, so if you run npm run build, it will fire off prebuild first. The browsers:update script will update the browsers list database for the latest browser versions, it’s important to do that before production builds so everything stays current.

From now on, instead of viewing your site in development mode on port 3001, you’ll use port 3000, which is the Webpack dev server.

Bundles

If you start your project now, you’ll notice that none of the CSS or JS files are working, that’s because we need to create the bundle files.

CSS

First, we’ll fix the CSS by creating a critical.css file for the home page (note that you will need to restart the development server when new bundle files are added to src/kind or src/path):

Ideally you’d only want styles that apply “above the fold” to be in a critical.css file, everything else should go in main.css which will be lazy loaded.

Notice that this only applies to the home page, if you navigate to anothe page all the styles are gone. To fix this, you should open the “Elements” tab in your browsers developer tools, and search for “query”, you’ll find two meta tags (remember these are only rendered in develop mode, not in production builds), here’s what they should look like for the /blog/ page:

This makes it easy enough to figure out what file created the bundles for any page, go ahead and add the global styles to all the pages on the demo site as an exercise.

JavaScript

First, let’s break up our lazy load function into re-usable bits. We will want to wait for document.readyState to be complete in many scripts, so we’ll move that code to a new file.

Next, we’ll just take out the wait for DOM code from the lazy load file.

Finally, we can combine the two in the kind and path bundle files.

Now we just need to add the lazy load script as we did above to all bundles for pages that might have images.

You should be able to start the project now and see a fully functional site!

If you run npm run build and then npx serve build, this will emulate production builds, but you’ll find the blog pages get a 404. These is because all of the posts are drafts, simply remove the line draft: true from first-post.md and second-post/index.md, then re-run npm run build and npx serve build.

Caching

Since we are using cache-busting by adding a hash of each asset file to their respective filenames, we can be aggresive with caching and know that any changes will be downloaded by the browser because the filename will change. Here are my cache control headers in netlify.toml format:

Summary

We now have Webpack fully integrated with Hugo, we can create page type and path specific bundles for JavaScript and CSS, and can optimize page loads by splitting CSS and Javascript bundles into critical and non-critical loading methods.

In the next segment, we will set up a React app inside of a Hugo page, which we’ll be able to do one or more times on any page to add small or even large reactive applications to any page on the site, make sure you so you don’t miss it!

Sponsors