★ Going serverless with Hugo and Netlify
Tags: serverless
Published: 2020-01-02 23:31:56

---
title: ★ Going serverless with Hugo and Netlify
published: true
date: 2019-07-09 09:30:48 UTC
tags: serverless
canonical_url: https://freek.dev/1396-going-serverless-with-hugo-and-netlify
---

[Our team](https://spatie.be) releases a lot of [open source packages](https://spatie.be/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](https://docs.spatie.be/laravel-medialibrary/v7/introduction/) and [event projector](https://docs.spatie.be/laravel-event-projector/v2/introduction/) get documented on [our documentation site](https://docs.spatie.be).

We recently moved our site from a Digital Ocean server to [Netlify](https://netlify.com/), 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](https://github.com/spatie/docs.spatie.be-old). All content was included in that repo as [a set of markdown files](https://github.com/spatie/docs.spatie.be-old/tree/c35ca9cdbff9f6cee5c947d51aec71c024417baf/resources/views). 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](https://netlify.com/). 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](https://github.com/spatie/laravel-medialibrary/tree/master/docs) of the package itself.

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

![Webhook settings on GitHub](https://freek.dev/uploads/media/docs-netlify/webhook.png)

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](https://gohugo.io/) to build [the docs.spatie.be site](https://docs.spatie.be). Let's take a look at how that works under the hood.

[The new docs.spatie.be repo](https://github.com/spatie/docs.spatie.be) 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](https://freek.dev/uploads/media/docs-netlify/build-settings.png)

In the repo, there's a [netlify.toml](https://github.com/spatie/docs.spatie.be/blob/ddcdac643f621e174005c8d5b303dd09886dd2f1/netlify.toml#L2) file that contains instructions on how the site should be built. [This](https://github.com/spatie/docs.spatie.be/blob/ddcdac643f621e174005c8d5b303dd09886dd2f1/netlify.toml#L2) 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](https://github.com/spatie/docs.spatie.be/blob/ddcdac643f621e174005c8d5b303dd09886dd2f1/fetch-content.js).

```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](https://github.com/spatie/docs.spatie.be/blob/ddcdac643f621e174005c8d5b303dd09886dd2f1/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](https://gohugo.io/content-management/organization/). Hugo will parse each markdown file and use the templates in [the `layouts` directory](https://github.com/spatie/docs.spatie.be/tree/ddcdac643f621e174005c8d5b303dd09886dd2f1/layouts/_default) to convert them to HTML. It's our feeling the learning [the Hugo templating language](https://gohugo.io/templates/) was one of the hardest things in this project.

In each `docs` directory of a repo, there is a [`_index.md`](https://github.com/spatie/laravel-medialibrary/blob/master/docs/_index.md) file with some general settings. Each subdirectory also has an [`_index.md`](https://github.com/spatie/laravel-medialibrary/blob/master/docs/converting-images/_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](https://twitter.com/riasvdv) and [Seb](https://twitter.com/sebdedeyne), who researched Netlify & Hugo and put all of this together.