Use a single Hugo codebase to generate content for different audiences

April 17, 2020

I’ve built a website with static content for my AWS-education program. This website is used by different type of users. Students, pro-level students, and trainers. I might want to add more types of users in the future. I want to use the same code base and host different rendered websites for each group. At the end of this blog post you can use the following command to generate a website for a specific audience:

hugo -e professional
hugo -e trainer
hugo server -e trainer

It took some time to figure out what feature I could use to get this done properly. Abusing the multilanguage system didn’t work out well. I decided to use the configuration system. According the documentation, the main purpose is to have a different production and development website (aka environment). I think I can use that!

Never heard of Hugo?
Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again. Learn more. It works in a few simple steps:

  1. Download/install hugo (brew install hugo).
  2. Create a /config.yaml with a few settings
  3. Create a /content directory with your site structure in directories and some markdown files (i.e. index.md)
  4. Create (or download) a theme and store this in /themes
  5. Run hugo server to live render the website on your computer, or just hugo and upload the /public folder to a static website hosting like S3 or Amplify.

This blog post is structured in three steps:

  1. Update the site configuration
  2. Using shortcodes to authorize access to content blocks
  3. Show or hide a complete page from users

Enjoy reading!

1. Update the site configuration

I only have a single config.yaml file in the root of my project. To get more environments, I’ve to create a directory /config/_default. The old config is then copied to /config/_default/config.yaml. If you have a config.toml, that works the same. Then I create 2 new directories: /config/professional and /config/trainer. Now I create an empty file params.yaml and config.yaml in each of these folders. This is the result:

├── config/
│   ├── _default
│   │   └── config.yaml
│   ├── professional
│       ├── config.yaml
│   │   └── params.yaml
│   └── trainer
│       ├── config.yaml
│       └── params.yaml
├── content
│   └── ...
├── themes
    └── ...

The /config/trainer/config.yaml contains a different title for this Site. You could of course change all other settings, they are merged with the default. Both /config/professional/params.yml and /config/trainer/params.yaml contain a parameter audience that contains a list of audiences. For example, the trainer configuration has access to all content:

audience: ["professional", "trainer"]

Configure Hugo

2. Using shortcodes to authorize access to content blocks

2.1 Layout

Create a Hugo shortcode in your theme /theme/themename/layouts/shortcodes/audience.html or your site /layouts/shortcodes/audience.html, the last one is recommended. Add the following code to that file. If audience is not set, it just shows the page. If audience is set, it will check if the audience is also in the site configuration. If yes, it shows the page. If no, it writes some hidden comments in HTML. If a second string was provided, it will show this information in the browser. For example: “Validate your results together with your trainer.".

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{- if in .Site.Params.audience (.Get 0) }}
  {{- .Inner }}
{{- else }}
  {{- "<!-- content hidden, accessible for: " | safeHTML }}
  {{- .Get 0 | safeHTML }}
  {{- " -->" | safeHTML }}
  {{- if (.Get 1) }}
    {{- (.Get 1) | safeHTML }}
  {{- end }}
{{- end }}

2.2 Content

Now edit a content file where you want to add a piece of content being showed or hidden, depending on the configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---
title: "Mixed Content"
---

This is visible for everyone!

{{% audience "professional" %}}
Only visible for professional
{{% /audience %}}

{{% audience "trainer" "sorry, trainer only" %}}
Only visible for trainer
*Markdown still works, and shortcodes too:**
{{< youtube w7Ft2ymGmfc >}}
{{% /audience %}}

{{% audience "professional" %}}
Only visible for professional
{{% audience "trainer" %}}
Only visible for professional AND trainer
Nested audiences also works
{{% /audience %}}
{{% /audience %}}

3. Show or hide a complete page from users

3.1 Layout

The previous discussed option is an ideal way to block access to content blocks within a page. To block a complete page, you could copy the following template, find {{ .Content }} in the templates. If you use a third party template, I recommend to first copy the layouts from /themes/themename/layouts/ to /layouts/ and only change those, leaving the third party theme intact.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{{- if .Params.audience }}
  {{- if in .Site.Params.audience (.Params.audience) }}
    {{- .Content }}
  {{- else }}
    <h1>Access Denied</h1>
    This page is only accessible for {{ .Params.audience }}
  {{- end }}
{{- else }}
  {{- .Content }}
{{- end }}

3.2 Content

Now add audience: "professional" to the frontmatter of another page. A page that you want to hide for users. If the site was rendered for the specified audience, the page will be visible, otherwise it shows an Access Denied.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
title: "Page only visibile for audience"
audience: "professional"
---

## This page is only visible for professional

{{% audience "trainer" %}}
And this content block only for trainers
{{% /audience %}}

Conclusion

We have learned how to make use of a single codebase for a Hugo static website, and let it generate different content for different audiences. I host every page with an S3 bucket. And I use a CloudFront distribution with a Lambda@Edge custom authorizer to protect access to these websites. I might want to write my own blog post about this setup somewhere in the future.

If you have any feedback, please let me know.

-Martijn

Photo by Nicholas Green on Unsplash

Tags
hugo
Author(s)
Martijn van Dongen
AWS Tribe Lead
Contact
Martijn van Dongen
Free Agent / AWS Tribe Lead

+31651175017
martijn@hitthecloudrunning.com
KVK / VAT on request