Apr 21 2018
Faster content rebuilding for Netlify's victor-hugo boilerplate

(scroll down to continue)

Whether you're just learning Hugo or bringing your Hugo experience to a new project, there's a good chance you'll be reaching for Netlify's victor-hugo boilerplate. It's the first starter kit listed in the Hugo documentation and has earned over 750 shiny Github stars ⭐️.

Here's a summary of what's in the boilerplate:

  • gulp for watching files and running tasks
  • Hugo for building the content in the site directory
  • webpack for building JavaScript in src/js to dist/app.js
  • PostCSS for building CSS from src/css to dist/main.css
  • Browsersync for live-reloading the browser in development

The great thing about victor-hugo and boilerplates in general is that you don't have to know exactly what every piece does before you can start using it. To get up and running with victor-hugo, you just need a few commands:

$ git clone git@github.com:netlify/victor-hugo.git

$ cd victor-hugo

$ npm install

$ npm start

Navigate to http://localhost:3000/ and you'll be greeted by a webpage 🎉

Reloading changes

When you make changes to your assets in the src folder or your Hugo content in the site folder, gulp tasks will rebuild the content and the browser will refresh automatically. For small and medium-size sites this all happens in a few milliseconds, making for a very pleasant developer experience.

Here's how it works under the hood. The server gulp task watches for changes in site/**/* and then spawns a hugo process to build the site, which exits when it's done. Once the gulp task detects that the hugo process exited successfully, it notifies Browsersync to refresh the page. The code is in the buildSite() method in gulpfile.babel.js:

return spawn(hugoBin, args, {stdio: "inherit"}).on("close", (code) => {
  if (code === 0) {
    browserSync.reload();
    cb();
  } else {
    browserSync.notify("Hugo build failed :(");
    cb("Hugo build failed");
  }
});

Speeding up reloads for content

As the amount of content for your static site grows, however, this method might not build changes as fast as you want. Each new hugo process has to read all the content from disk and the time to do that increases as your site grows. For my site of around 90 pages, the rebuilding didn't quite feel instant anymore.

There is an easy way to make this process faster and more efficient. The trick is not to launch a new Hugo process (which does a full rebuild) on every change, but instead to use Hugo's built-in ability to serve & watch files with incremental rebuilds, also known as Fast Render Mode.

Hugo has a server subcommand that runs a built-in HTTP server and accepts a -w flag that watches for underlying files. As the Hugo docs mention, this has performance advantages:

"hugo server will avoid writing the rendered and served content to disk, preferring to store it in memory."

dzello/victor-hugo

I created a fork of victor-hugo that adds a gulp task to take advantage of this Hugo feature. The task is called server-hugo and it uses the Hugo HTTP server in watch mode, just as if you ran hugo server -w.

Here's how the server-hugo task looks:

// Development server with hugo --watch
gulp.task("server-hugo", ["css", "js", "fonts"], (cb) => {
  browserSync.init({
    proxy: "localhost:3001",
    ui: { port: 3002 }
  });
  gulp.watch("./src/js/**/*.js", ["js"]);
  gulp.watch("./src/css/**/*.css", ["css"]);
  gulp.watch("./src/fonts/**/*", ["fonts"]);
  watchSite(cb);
});

Here's the watchSite method that it calls:

/*
* Run hugo in watch mode
*/
function watchSite(cb, options = {}, environment = "development") {
  const args = ["server"].concat(hugoArgsDefault).concat(options).concat(["-w", "-p", "3001"]);

  process.env.NODE_ENV = environment;

  return spawn(hugoBin, args, {stdio: "inherit"}).on("close", (code) => {
    if (code === 0) {
      cb();
    } else {
      cb("Hugo process exited");
    }
  });
}

In order to avoid losing any live-reload functionality from the original server task, this task uses Browsersync's proxying feature. Browsersync (on port 3000) sits in front of Hugo (on port 3001) so that it can inject its own instance of LiveReload into the page. With this setup, the browser will reload (or apply CSS changes) instantly after both Hugo and and asset rebuilds.

--disableFastRender

As of 0.3.0, Hugo speeds up live reloads by building only the parts of the site that changed. It's a great feature, squarely in the tradition of Hugo's relentless need for speed. However, in my experience it can lead to times where a change to one template or partial doesn't rebuild every page that it's included in.

If you run into that same issue, you can turn off incremental builds by adding a --disableFastRender argument to the hugo server command. The builds take slightly longer but the upside is that you'll never have to worry about stale content.

To add --disableFastRender to the gulp task above, just modify this line like so:

const args = ["server"].concat(hugoArgsDefault).concat(["-w", "-p", "3001", "--disableFastRender"]);

Happy boilerplating!

Question or comment?