Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

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.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month 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.


What are your thoughts on "Going serverless with Hugo and Netlify"?

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.