In order to be a more competent developer, I thought that I would need to be able to document and explain things simply. And so I started one blog to explain my technical tutorials and observations. Through that blog I would explain things that I’ve learned over the years.

A relative recommended Hugo to me a while ago for handling my blogs (I started three), and I found that it was fast and nifty. Other blogs did not attract me because of how bulky or old they were. Hugo does have a niche templating system, but I was able to figure it out relatively quick. Along with my learning, I was able to build a configurable theme for all three of my sites. This did get a little tricky, because I had all three sites in three different repos, and management of the three sites became brittle.

Over time, I came to a conclusion that I could swap my blog over to a Next.JS monorepo so that I could keep up-to-date with Next and unify theme management. After completing the transition, I found that there was simply too much customization needed in order to make a blog with Next.JS compared to using Hugo. I really wanted to keep things trim. I didn’t swap back to using Hugo yet, because I just didn’t know how to manage a monorepo with Hugo. Eventually, I found out that “content sections”, with a little bit of configuring, could effectively make a monorepo!

Content sections are found underneath the /content folder in the root of a Hugo project. Simply put, it’s just a logical grouping of content. For me, I separated my content into a life blog, development blog, and creative writing. Hugo will comb through all the content structure and generate HTML files based off the theme. With a default site, these content pages will all appear under the same site. So what did I do to have them all under different sites?

The key is having an index content file for one section. An index content file allows the section to have its own “home page”. With that, I set up redirect rules in Netlify from the root of a domain to the content section index. A user will be able to navigate to one of these websites that I have and only see the content related to that website they’re going to. The additional bonus that I needed, in order to build SEO, was to generate the apprpriate SEO files per main section. In the [outputs] section in the hugo.toml file, I specified Hugo to make sitemap, robots, rss, and manifest files for each section. This required that I wrote a template for the specific files. And with that, I have a monorepo-esque repository handling all my websites that I write content to!


In the spirit of sharing, here is some of the code that I previously mentioned.

A developer can tell hugo to just generate outputs for section.

1
2
3
[outputs]
home = ['html']
section = ['html', 'sitemap', 'json', 'robots', 'rss', 'manifest']

Here is one example of a Netlify redirect I configured in a ntelify.toml file.

1
2
3
4
5
[[redirects]]
from = "https://gremlich.me/index.xml"
to = "https://gremlich.me/me/index.xml"
status = 200
force = true

Here are the template files to aid SEO for sections. These were found in /layouts/_default.

Filename: section.sitemap.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
{{ $currentSection := "" }}
{{ if .Section }}
  {{ $currentSection = .Section }}
{{ else }}
  {{ $currentSection = "me" }}
{{ end }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{{ range .CurrentSection.RegularPages }}
  <url>
    <loc>https://gremlich.{{$currentSection}}{{ .RelPermalink }}</loc>{{ if not .Lastmod.IsZero }}
    <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
    <xhtml:link
                rel="alternate"
                hreflang="{{ .Language.Lang }}"
                href="{{ .Permalink }}"
                />{{ end }}
    <xhtml:link
                rel="alternate"
                hreflang="{{ .Language.Lang }}"
                href="{{ .Permalink }}"
                />{{ end }}
  </url>
{{ end }}  
</urlset>

Filename: section.rss.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
{{ $currentSection := "" }}
{{ if .Section }}
  {{ $currentSection = .Section }}
{{ else }}
  {{ $currentSection = "me" }}
{{ end }}

{{- $pctx := . -}}
{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}}
{{- $pages = $pages | first $limit -}}
{{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>{{ .Title }}</title>
    <link>https://gremlich.{{ $currentSection }}</link>
    <description>{{ .Description }}</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>{{ site.Language.LanguageCode }}</language>{{ if not .Date.IsZero }}
    <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
    {{- with .OutputFormats.Get "RSS" -}}
    {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
    {{- end -}}
    {{ range $pages }}
    <item>
      <title>{{ .Title }}</title>
      <link>https://gremlich.{{ $currentSection }}{{ .Permalink }}</link>
      <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
      {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
      <guid>{{ .Permalink }}</guid>
      <description>{{ .Summary | html }}</description>
    </item>
    {{ end }}
  </channel>
</rss>

Filename: section.robots.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!-- section.robots.txt -->
{{ $currentSection := "" }}
{{ if .Section }}
  {{ $currentSection = .Section }}
{{ else }}
  {{ $currentSection = "me" }}
{{ end }}

User-agent: Googlebot
Disallow: /nogooglebot/

User-agent: *
Allow: /

Sitemap: http://www.gremlich.{{$currentSection}}/sitemap.xml

Filename: section.manifest.webmanifest

I had to specifiy a custom output format in hugo.toml file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "name": "{{ .Title }}",
  "description": "{{ .Description }}",
  "short_name": "{{ .Params.short_name }}",
  "theme_color": "#fde047",
  "background_color": "#0f172a",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait"
}
1
2
3
4
5
[outputFormats]
[outputFormats.manifest]
mediaType = "application/manifest+json"
baseName = "manifest"
isPlainText = true