I'm currently organising Full Stack Europe. It's a conference in Antwerp, Belgium for developers who want to learn across the stack. You can use this link get your ticket with a nice discount.

Going serverless with Hugo and Netlify

Original – by Freek Van der Herten – 5 minute read

Our team releases a lot of open source packages. All of our packages are well documented. For the smaller packages, we use a simple readme on GitHub. The bigger packages, like medialibrary and event projector get documented on our documentation site.

We recently moved our site from a Digital Ocean server to Netlify, a serverless platform. In this post, I'd like to tell you why and how we did this.

A good old Laravel app

Until a few weeks ago, our documentation site was a Laravel app. All content was included in that repo as a set of markdown files. Whenever a markdown file got updated, we had a webhook notify our server which would pull in the changes.

For a long time, this approach worked pretty well for us. There were two downsides. First, the documentation of a package wasn't in the package repo, but in the docs.spatie.be repo. When getting PRs, we had to ask contributors to send an extra PR to docs.spatie.be to update the documentation. Secondly, we had to manage a Digital Ocean server ourselves to host the site.

Moving to Netlify

A couple of weeks ago, we finished our transition to Netlify. Because of the serverless nature of Netlify, we don't have to manage a server ourselves anymore to host docs.spatie.be. All documentation of a packag now lives in the docs directory of the package itself.

On each package that has a docs directory, we added a webhook to Netlify.

Webhook settings on GitHub

So each time something is committed, Netlify will get notified. At that time, our deploy and build script will run. That script will, in short, download the docs directories of all repos, and use a static site builder called Hugo to build the docs.spatie.be site. Let's take a look at how that works under the hood.

The new docs.spatie.be repo does not contain a Laravel app anymore, but the Netlify configuration for docs.spatie.be.

At Netlify we've added that repo in the "Build settings".

Build settings on Netlify

In the repo, there's a netlify.toml file that contains instructions on how the site should be built. This is the command that 'll build the website.

command = "node fetch-content.js && hugo -b https://docs.spatie.be"

Let's take a look at the content of fetch-content.js.

const util = require("util");
const exec = util.promisify(require("child_process").exec);
console.log('Fetching repositories...');
console.time('Fetched repositories');

const repositories = require("./repositories.json");

function transformBranchToFolderName(branch) {
    return branch.startsWith('v') ? branch.substring(1) : branch;

(async function () {
    let promises = [];

    for (const repository of repositories) {
        for (const [branch, alias] of Object.entries(repository.branches)) {
            const folder = `content/${repository.name}/${alias}`;
            const url = `https://codeload.github.com/${repository.repository}/tar.gz/${branch}`;

            promises.push(exec(`mkdir -p ${folder} && curl ${url} \
             | tar -xz -C ${folder} --strip=2 ${repository.repository.split('/')[1]}-${transformBranchToFolderName(branch)}/docs \
             && echo "---\ntitle: ${repository.name}\ncategory: ${repository.category}\n---" > content/${repository.name}/_index.md`));

    await Promise.all(promises);
    console.timeEnd('Fetched repositories');

The code above will loop over each of the repositories defined in repositories.json. For each repo a promise will be made that will fetch the code of the repo as a zip file and unzip it in a local content directory. You might think downloading all repos like this is slow, but it's very fast. It only takes a couple of seconds.

After node fetch-content.js has completed, hugo -b https://docs.spatie.be will be executed. This command will build the actual site. Hugo is installed by default on Netlify.

The directory structure of the locally created content directory will be used to determine the URL structure. You can read more on that in the Hugo docs. Hugo will parse each markdown file and use the templates in the layouts directory to convert them to HTML. It's our feeling the learning the Hugo templating language was one of the hardest things in this project.

In each docs directory of a repo, there is a _index.md file with some general settings. Each subdirectory also has an _index.md to specify its title and order in the navigation.

In closing

It's cool that by moving to Netlify, we don't need to manage a server for our docs site anymore. The fact that the documentation of a package now lives in the repo of the package itself is also a nice win.

A big thank you to my colleagues Rias and Seb, who researched Netlify & Hugo and put all of this together.

Stay up to date with all things Laravel, PHP, and JavaScript.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Vaibhavraj Roham retweeted on 13th July 2019
Kevin Woblick replied on 13th July 2019
Nice to see so many people hopping on the train to use Hugo and static pages. Just moved my blog from Wordpress to Hugo and I am really excited about how easy it was. 👍 (blog.kovah.de/en/2019/static…)
Lee Cox liked on 11th July 2019
Bhavdip B Pambhar liked on 10th July 2019
Marco liked on 10th July 2019
Sergio Ródenas liked on 9th July 2019
Evil Commissar liked on 9th July 2019
Julio Serpone liked on 9th July 2019
NorthStack® liked on 9th July 2019
Felipe 🍕 liked on 9th July 2019
Spatie retweeted on 9th July 2019
ArielSalvadorDev retweeted on 9th July 2019
ArielSalvadorDev liked on 9th July 2019
Rias Van der Veken liked on 9th July 2019
Cristian Pallarés replied on 9th July 2019
Need to check it out 👍 I'm currently using Nuxt for my static pages.
Pham Tri Dung liked on 9th July 2019
Axel Pardemann liked on 9th July 2019
Michiel Kempen liked on 9th July 2019
Dinh Quoc Han 🐳 liked on 9th July 2019
Vaibhavraj Roham liked on 9th July 2019
Leandrit Ferizi liked on 9th July 2019
Marcus liked on 9th July 2019
Cyril de Wit liked on 9th July 2019
Iftekher Sunny liked on 9th July 2019
Tom Van Looy liked on 9th July 2019
Antonis Sideris liked on 9th July 2019
Pablo Román liked on 9th July 2019
Serverless Fan retweeted on 9th July 2019
CloudCoop ☁️ retweeted on 9th July 2019
Erik Oberhausen liked on 9th July 2019
Feralheart liked on 9th July 2019
Shreyansh 💻 🖱️ liked on 9th July 2019
Sebastian De Deyne retweeted on 9th July 2019