Introduction#

Howdy! So we recently went through the process of making this website, and I had a few frustrations with the guide we followed. This guide aims to be easy to use, and guide you on how to get a blog started with as little effort as possible, AND add a frontend that can easily be accessed anywhere to edit content using Decap CMS. Currently, there is an issue with Decap CMS’s Slate Rich Text Editor which… causes crashing if you backspace a bullet point. This can be circumvented by using the Markdown Editor instead, which is built in by default, or by simply not using bullet points! Soren attempted using a replacement for Decap CMS, known as Sveltia CMS, however authentication issues caused this to not be a good option for the time being. We will make an updated post if the authentication issues are resolved! Credit to Arsh Sharma for the original guide, which can be found here: Building a Blog With a CMS Using Hugo and Netlify

Requirements#

  • Git + GitHub Desktop (GitHub Desktop will install Git. It’s worth noting that a GitHub account is also required.)
  • Hugo Extended (At minimum, v0.90.x. is required for this guide. Ideally, use the most up to date release.)
  • Go
  • A Netlify Account
  • A Hugo Theme (This guide will focus on using the Terminal theme, which this website currently uses!

Getting Started#

To get started, we’ll be creating a new site locally using Hugo! Hugo is a static site generator which allows you to make extremely lightweight and fast websites. This does however come at the expense of usually not allowing for many responsive elements. Regardless, it makes for an excellent blogging or showcase framework!

Now, let’s go ahead and get our site started! You can run this command anywhere, though preferably somewhere you can easily access:

hugo new site my-blog

cd my-blog

With these two commands, you should now have your terminal pointing at your new site. Now, let’s go ahead and add a theme! As stated above, we used Terminal, though you may use any theme you’d like! The general ideas of this guide should carry over to most themes, specifically those added as a git submodules. Now, let’s go ahead and initiate our git repository, and then add our theme with the following two commands!

git init

git submodule add -f https://github.com/panr/hugo-theme-terminal.git themes/terminal

Please note that in some situations, you may encounter an error when installing your Terminal theme. In the rare situation you encounter an error, please remove theme = "terminal" from your config file.

If no errors were encountered, please ensure that within your Hugo configuration file, (typically, hugo.toml, in this case), you have theme = "terminal". This will allow your website to load your theme!

If you’d like to take a look, you can run:

hugo server

This will serve your website at http://localhost:1313/. However, things look rather… Empty.

Configuration#

No worries! This is due to the fact that our Hugo configuration file is still rather empty. Let’s go ahead and add some parameters! We can copy the example from Terminal’s repo, or you can use the following. I’ve added in a few extra comments, however I highly advise reviewing Terminal’s documentation for even more information.



baseurl = "/"
languageCode = "en-us"

# Add it only if you keep the theme in the `themes` directory.

# Remove it if you use the theme as a remote Hugo Module.

theme = "terminal"
paginate = 5

[params]

# dir name of your main content (default is `content/posts`).

# the list of set content will show up on your index page (baseurl).

  contentTypeName = "posts"

# if you set this to 0, only submenu trigger will be visible

# To clarify, any additional menu items past one will only be shown by clicking the drop down.

  showMenuItems = 1

# show selector to switch language

  showLanguageSelector = false

# set theme to full screen width

  fullWidthTheme = false

# center theme with default width

  centerTheme = false

# if your resource directory contains an image called `cover.(jpg|png|webp)`,

# then the file will be used as a cover automatically.

# With this option you don't have to put the `cover` param in a front-matter.

  autoCover = true

# set post to show the last updated

# If you use git, you can set `enableGitInfo` to `true` and then post will automatically get the last updated

  showLastUpdated = false

# Provide a string as a prefix for the last update date. By default, it looks like this: 2020-xx-xx \[Updated: 2020-xx-xx] :: Author

# updatedDatePrefix = "Updated"

# whether to show a page's estimated reading time

# readingTime = false # default

# whether to show a table of contents

# can be overridden in a page's front-matter

# Toc = false # default

# set title for the table of contents

# can be overridden in a page's front-matter

# TocTitle = "Table of Contents" # default

[languages]
  [languages.en]
    languageName = "English"
    title = "my-blog" # This of course, should be your blog's own name.

[languages.en.params]
  # A majority of these are self explanatory. Most config files will also have documentation.
  subtitle = "Hello, World!"
  owner = ""
  keywords = ""
  copyright = ""
  menuMore = "Show more"
  readMore = "Read more"
  readOtherPosts = "Read other posts"
  newerPosts = "Newer posts"
  olderPosts = "Older posts"
  missingContentMessage = "Page not found..."
  missingBackButtonLabel = "Back to home page"
  minuteReadingTime = "min read"
  words = "words"

  [languages.en.params.logo]
    logoText = "my-blog"
    logoHomeLink = "/"

  [languages.en.menu]
    [[languages.en.menu.main]]
      # Your about.md should be sored under /content/about.md. 
      # You may create additional menu items by copying these parameters and editing them.
      identifier = "about"
      name = "About"
      url = "/about"

The above configuration file should be enough to get you started, and start displaying your post! Oh, wait… we haven’t made any post yet!

As a final test before we get into Netlify deployment and Decap CMS integration, let’s make sure that post are being displayed. We can easily do this by making a new post.

hugo new posts/hi-there/index.md

You can then open the “index.md”, file with any text editor, and edit anything you please! Though, it’s not necessary to worry about this too much at the moment. Once we get into Decap, you will not be meddling too much with the frontmatter.

As an additional treat, you can go to https://panr.github.io/terminal-css/, to create your own custom color scheme for Terminal! It’s extremely convenient, and all you need to do is follow the steps.

Deploying to Netlify#

Decap CMS has nearly seamless integration with Netlify; which is to be expected as they are the ones who open sourced the project. Firstly, create a GitHub repo, and commit and push your local git repo to it. This can easily be done with GitHub Desktop, which allows you to link your account, select a local repo, and upload it to GitHub.

WARNING: Before uploading: Please rename hugo.toml , to, config.toml. Netlify expects these parameters, and will fail to build if this is not changed.#

Once your GitHub repo is uploaded, you can chose to either have it public or private. Since we’ll be using Netlify for authentication, privacy settings are not important.

Netlify will then allow you to create a website through an existing GitHub repo. Netlify will also automatically detect that you are importing a Hugo project, and populate the build instructions. Simply follow the steps provided, and your website will be then be hosted.

Adding Decap CMS#

This is potentially the most important step, as it will allow us to go ahead and allow us to add an open source frontend interface known as Decap CMS. You can read all about it on their site, and their documentation. This guide will not go into specifics regarding widgets and such for the sake of simplicity. It is highly advised to review the documentation for additional insight.

The first step here will be to create and admin folder inside of the static folder located in the root of your website. Within this admin folder, you’ll need to create:

index.html

<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="robots" content="noindex" />
    <title>Content Manager</title>
    <!-- This will be used to add your Netlify authentication -->
    <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</head> #

<body>
    <!-- Include the script that builds the page and powers Decap CMS -->
    <script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
</body>

</html>

Second, you’ll need to add your config.yml . This file will dictate how Decap CMS creates new content on your website, and necessary pathing. Please feel free to use the following configuration, which was made for use in Terminal.

config.yml

#static/admin/config.yml

backend:
  name: git-gateway
  branch: master # Please rename if necessary to match your master/main branch.
media_folder: "static/images" # Folder where user uploaded files should go
public_folder: "images"
collections:
  - name: "posts" # Used in routes, e.g., /admin/collections/post
    label: "Posts" # Used in the UI
    folder: "content/posts" # The path to the folder where the documents are stored
    path: "{{slug}}/index"
    media_folder: "" # Save images in the post's own folder instead of the static folder
    public_folder: ""
    create: true # Allow users to create new documents in this collection
    fields: # The fields for each document, usually in front matter
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Author", name: "author", widget: "string" }
      - { label: "Publish Date", name: "date", widget: "datetime" }
      - { label: "Featured Image", name: "image", widget: "image",hint: "Make sure the file is named cover.png|jpg|jpeg", required: false}
      - { label: "Tags", name: "tags", widget: "list", summary: "{{fields.tag}}", field: { label: "Tag", name: "tag", widget: "string" }} # https://github.com/decaporg/decap-cms/issues/4646 
      - { label: "Show Full Content", name: "showFullContent", widget: "boolean", default: false, required: false}
      - { label: "Table of Contents", name: "toc", widget: "boolean", default: false, required: false}
      - { label: "ToC Title", name: "TocTitle", widget: "string", required: false }
      - { label: "Description", name: "description", widget: "text", required: false }
      - { label: "Body", name: "body", widget: "markdown"}

As a short explanation, widgets allow us to add information to our post’s frontmatter. This allows us to then edit the fields to our liking. You can read more about widgets, here.

Intermission#

Before we move on, I would advise taking this opportunity to also add in your Netlify authentication widgets. One has already been added above:

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

Another must be added to the <head> section of your website. Since we’re using a custom theme, you must copy your files over to /layouts/partials/

For this theme, you may use the following files, which were copies from the original theme’s layout folder, and edited to add in the widget.

head.html



```

<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ if .IsHome }}{{ $.Site.Params.Subtitle }}{{ else if .Description}}{{ .Description | plainify }}{{ else }}{{ .Summary | plainify }}{{ end }}" />
<meta name="keywords" content="{{ with .Params.Keywords }}{{ delimit . ", " }}{{ else }}{{ $.Site.Params.Keywords }}{{ end }}" />
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
{{ if .Params.noindex }}
  {{ if or (eq (.Param "noindex") true) (eq (.Param "noindex") "true") }}
    <meta name="robots" content="noindex" />
  {{ end }}
{{ else }}
  <meta name="robots" content="noodp" />
{{ end }}
<link rel="canonical" href="{{ .Permalink }}" />

{{ template "_internal/google_analytics.html" . }}

{{ $css := resources.Match "css/*.css" }}
{{ range $css }}
  {{ $styles := . | minify | fingerprint }}
  <link rel="stylesheet" href="{{ $styles.Permalink }}">
{{ end }}

<!-- Custom Terminal.css styles -->
{{ if (fileExists "static/terminal.css") -}}
  <link rel="stylesheet" href="{{ "terminal.css" | absURL }}">
{{- end }}
<!-- Custom CSS to override theme properties (/static/style.css) -->
{{ if (fileExists "static/style.css") -}}
  <link rel="stylesheet" href="{{ "style.css" | absURL }}">
{{- end }}

<!-- Icons -->
<link rel="shortcut icon" href="{{ "favicon.png" | absURL }}">
<link rel="apple-touch-icon" href="{{ "apple-touch-icon.png" | absURL }}">

<!-- Twitter Card -->
<meta name="twitter:card" content="summary" />
{{ if (isset $.Site.Params "twitter") }}
  {{ if (isset $.Site.Params.Twitter "site") }}
    <meta name="twitter:site" content="{{ $.Site.Params.Twitter.site }}" />
  {{ end }}
    <meta name="twitter:creator" content="{{ if .IsHome }}{{ $.Site.Params.Twitter.creator }}{{ else if isset .Params "authortwitter" }}{{ .Params.authorTwitter }}{{ else }}{{ .Params.Author }}{{ end }}" />
{{ end }}

<!-- OG data -->
<meta property="og:locale" content="{{ $.Site.Language.Lang }}" />
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta property="og:title" content="{{ if .IsHome }}{{ $.Site.Title }}{{ else }}{{ .Title }}{{ end }}">
<meta property="og:description" content="{{ if .IsHome }}{{ $.Site.Params.Subtitle }}{{ else if .Description}}{{ .Description | plainify }}{{ else }}{{ .Summary | plainify }}{{ end }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:site_name" content="{{ $.Site.Title }}" />
{{ if (isset .Params "cover") }}
  {{ $pageCover := .Param "cover" }}
  {{ with (.Resources.GetMatch (.Param "cover")) }}
    {{ $pageCover = .RelPermalink }}
  {{ end }}
  <meta property="og:image" content="{{ $pageCover | absURL }}">
{{ else }}
  <meta property="og:image" content="{{ "og-image.png" | absURL }}">
{{ end }}
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="627">
{{ range .Params.categories }}
  <meta property="article:section" content="{{ . }}" />
{{ end }}
{{ if isset .Params "date" }}
  <meta property="article:published_time" content="{{ time .Date }}" />
{{ end }}

<!-- RSS -->
{{ with .OutputFormats.Get "RSS" }}
  <link href="{{ .RelPermalink }}" rel="alternate" type="application/rss+xml" title="{{ $.Site.Title }}" />
{{ end }}

<!-- JSON Feed -->
{{ with .OutputFormats.Get "json" }}
  <link href="{{ .RelPermalink }}" rel="alternate" type="application/json" title="{{ $.Site.Title }}" />
{{ end }}

<!-- Extended head section-->
{{ partial "extended_head.html" . }}

```

baseof.html


```
<!DOCTYPE html>
<html lang="{{ $.Site.Language }}">
<head>
  {{ block "title" . }}
    <title>{{ if .IsHome }}{{ $.Site.Title }}{{ else }}{{ .Title }} :: {{ $.Site.Title }}{{ end }}</title>
  {{ end }}
  {{ partial "head.html" . }}
</head>
<body>
{{ $container := cond ($.Site.Params.FullWidthTheme | default false) "container full" (cond ($.Site.Params.CenterTheme | default false) "container center" "container") }}

<div class="{{- $container -}}{{- cond ($.Site.Params.oneHeadingSize | default false) " headings--one-size" "" }}">

  {{ partial "header.html" . }}

  <div class="content">
    {{ block "main" . }}
    {{ end }}
  </div>

  {{ block "footer" . }}
    {{ partial "footer.html" . }}
  {{ end }}
</div>
<script>
  if (window.netlifyIdentity) {
    window.netlifyIdentity.on("init", user => {
      if (!user) {
        window.netlifyIdentity.on("login", () => {
          document.location.href = "/admin/";
        });
      }
    });
  }
</script>
</body>
</html>

```

Netlify Identity#

Now that we’ve gone ahead and added in Decap CMS and our authentication widgets, let’s enable Identity through Netlify. This will allow us to edit our content through Decap CMS, and push changes into our repository, which will then be reflected on our website.

Simply click on Site Configuration > Identity , then Enable Identity .

Under “Registration Preferences”, you may then select “Invite Only”. Personally, we found GitHub to be the best way to keep things in one place, therefore we recommend using GitHub as your external OAuth provider. Please select “Use Default Configuration”.

Afterwards, it’s necessary to go to the “Service”, section, and enable “Git Gateway”. Without this, you will not be able to push changes to your repo, and Decap will fail to load your content.

Finally, you may invite users under Identity > Users . Here, you can invite yourself using the email attached to your GitHub account. You may, of course, add other users if desired.

Finishing Touches#

If everything went well, you can now go to your-website.com/admin to access Decap CMS, and sign in through GitHub! You can also change your domain to a custom one on Netlify. Feel free to reach out to [email protected], or [email protected], for any additional assistance or troubleshooting.