Making sure developers face as little friction as possible when working on a project is challenging. During the creation of the engineering blog, we were deliberate about choosing tools that support us in providing a seamless workflow.
We aimed to quickly add, modify and extend our content with as little work as possible. Apart from content changes, it was also crucial for us to enable quick developer feedback when adding new features or tweaking existing code.
We now want to share how we have used Hugo alongside Laravel Mix to achieve the goals outlined above.
Using Hugo as a Static Site Generator
Early on during the development, we knew that serving static pages is the most fitting solution with regards to the goals and requirements we wanted to achieve.
Settling on Hugo came from the fact that we needed a platform that is familiar to most existing developers and easy to grasp for newcomers. For example, while our Backend engineers are used to Go, they might not have experience writing React code (if we were to use, e.g., Gatsby). However, engineers not familiar with Go are likely to pick up Hugo’s templating syntax quicker than those having to learn a full-fledged framework like React from scratch.
Built-in features such as hugo server
(a webserver supporting hot-reload), image processing, and advanced content structure
mechanisms have convinced us to use Hugo to launch our tech blog.
Adding Laravel Mix
Hugo already provides built-in support for handling assets (e.g., minification, building, bundling) through the usage of Hugo Pipes. However, we wanted to decouple the asset handling process from the templates to consolidate our build process configuration in one file, enabling us to take control of cache busting, the addition of frameworks like Tailwind, and the transpiling/minification process of our JavaScript files.
Discussing the options, Grunt, Webpack, and Laravel Mix were amongst the possible candidates. Ultimately, Laravel Mix was chosen
as an elegant wrapper around Webpack for the 80% use case. Using the provided API, we were able to define the process
steps needed for our project neatly. Here is an example of using tailwindcss
:
const mix = require('laravel-mix');
const tailwindcss = require('tailwindcss')
mix
.setPublicPath(distDir)
.postCss('src/css/tailwind.css', `${distCssDir}/tailwind.css`, [
require('postcss-import'),
tailwindcss('./tailwind.config.js'),
])
Beyond the build capabilities, Laravel Mix also enabled us to employ cache busting and reliable minification in production environments.
Combining the power of both
As mentioned earlier, providing quick feedback (e.g., through hot-reloading) is essential for us to ensure a smooth development workflow. It took us a bit of tinkering to make Hugo and Laravel Mix work flawlessly together, but the result was worth it.
Developers can use yarn
scripts for handling everyday use cases. The current script setup is defined as follows:
{
"scripts": {
"watch": "hugo serve -D --port 1234 --disableFastRender & yarn run build:assets:dev --watch",
"dev": "yarn run build:development",
"prod": "yarn run build:production",
"build:development": "yarn run build:assets:dev && hugo",
"build:production": "yarn run build:assets:prod && hugo",
"build:assets:dev": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"build:assets:prod": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}
}
A particular challenge is the implementation of file watching using both Laravel Mix and Hugo. The following diagram provides an entry point to our explanation.
Here’s the process deconstructed:
-
yarn watch
kicks off a Laravel Mix watch process as well ashugo server
-
Laravel Mix watch:
- Builds assets from a source directory (e.g.
src/css
) to thestatic/*
directory. Hugo’s default behavior automatically copies file contents fromstatic
topublic
, ultimately making these assets available in template files. - During the build, Laravel Mix creates a manifest file that can be used for cache-busting.
The manifest file is copied to the
data
directory as Hugo will then automatically pick it up to make it available to use in the templates. Here is an example of what it looks like:
{ "/css/tailwind.css": "/css/tailwind.css?id=b85e739694315d6d0f3f", "/js/shared/lazyload.es5.js": "/js/shared/lazyload.es5.js?id=037e42fcaf9dc0dd59bd" }
This file is then indexed in the templates:
<link rel="stylesheet" type="text/css" href='{{ index .Site.Data.Assets "/css/tailwind.css" }}'>
- On file changes, the process repeats.
- Builds assets from a source directory (e.g.
-
Hugo Server:
- Upon changes in the
data
directory (caused by a re-compilation by Laravel Mix as outlined above), Hugo will re-generate the pages again - Upon changes in the template files, Hugo will re-generate the pages as well
- Upon changes in the
Leveraging this combination, we can now make use of cache busting, minification, hot-reloading, and advanced build steps through the usage of these two unique tools.
Closing thoughts
Separating Hugo and Laravel Mix has achieved a workflow that enables fast feedback for developers working on new content and features. Using static pages, we can gain performance and SEO benefits while keeping the tooling slim. Laravel Mix enables us to define more complex build steps in a single configuration file instead of Hugo Pipe statements littered around templating files.
Written by
Patrick Ahmetovic
May 19, 2021
Patrick is a frontend developer based in Austria, focusing on the public-facing features of the refurbed platform.