Coding Blog using Hugo

Requirements

The home page must list all the articles (only the links with title) in the md files. Clicking the link will go to view article page that shows the title and body. It should support code syntax highlighting.

Creating a blog with Hugo is straightforward and manageable. Here are the general steps you need to follow:

Step 1: Install Hugo

First, you’ll need to install Hugo. It’s available for Windows, MacOS, and Linux. You can check out the official Hugo Installation Guide for specific instructions for your OS.

Step 2: Create a new Hugo site

Once you’ve installed Hugo, open your terminal and navigate to the directory where you want your new site to be. Then, run the following command:

1
hugo new site my-blog

Replace “my-blog” with the name you want for your blog.

Step 3: Add a theme

Next, you need to choose a theme for your blog. You can find a lot of great themes at Hugo Themes. For this guide, let’s use the “Ananke” theme. You can add it with the following commands:

1
2
3
cd my-blog
git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke

And add the theme to your site configuration:

1
echo 'theme = "ananke"' >> config.toml

Step 4: Create content

Now, you’re ready to start adding content. Hugo uses Markdown for its content files. To create a new post, use the following command:

1
hugo new posts/my-first-post.md

This will create a new file at content/posts/my-first-post.md. Open this file in your text editor and you’ll see something like:

1
2
3
4
5
---
title: "My First Post"
date: 2023-06-06T18:25:34+01:00
draft: true
---

Underneath, you can start writing your blog post in Markdown. For example:

1
2
3
4
5
6
7
8
9
---
title: "My First Post"
date: 2023-06-06T18:25:34+01:00
draft: false
---

# Welcome to my blog!

This is my first post. I'm really excited to start blogging!

Notice that draft has been changed to false. If draft is set to true, Hugo won’t include the page in the built site.

Hugo also supports code syntax highlighting out of the box using Chroma. You can use it like so:

1
2
3
```python
def hello_world():
    print("Hello, world!")

To make sure syntax highlighting is enabled, add the following lines to your `hugo.toml` file:

```toml
pygmentsCodeFences = true
pygmentsStyle = "monokai"

You can replace "monokai" with the name of any style included with Chroma.

Step 5: Build and Run the site

Finally, you can build your site with the following command:

1
hugo -D

And you can run your site locally with:

1
hugo server -D

Visit localhost:1313 in your browser to see your site. The -D flag tells Hugo to include content marked as a draft.

Clicking on the title of a post in the list on the homepage will take you to that post’s page, where you can see the title and content of the post.

Remember to save and commit any changes you make to your content. When you’re ready to publish your site, you’ll need to host it somewhere. GitHub Pages and Netlify are both good options that integrate

Using Fuse.js with Hugo

Sure, Fuse.js is another popular and powerful option for search functionality in static sites like those built with Hugo. Here are steps on how to integrate Fuse.js with Hugo:

Step 1: Adding HTML for the Search Box

First, you’ll need to modify your site’s header or the navigation bar to include a search box. The header is usually defined in the file layouts/partials/header.html or layouts/partials/nav.html (this can vary depending on the theme). You can add the following HTML code in the appropriate position where you want the search box to appear:

1
2
3
4
<form>
    <input type="text" id="search" placeholder="Search">
    <ul id="searchResults"></ul>
</form>

Step 2: Including the Necessary JavaScript Library

Next, you will need to include Fuse.js in your site. Add the following line to your layouts/partials/footer.html file or layouts/partials/scripts.html file (again, the exact file can vary depending on the theme):

1
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/6.4.6/fuse.basic.min.js"></script>

Step 3: Generating the Search Index File

To create the search index file that Fuse.js can use to perform searches, you need to add the following to your site’s configuration file (config.toml):

1
2
[outputs]
home = ["HTML", "JSON"]

Next, create a new file at layouts/index.json with the following content:

1
2
3
4
5
{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
    {{- $.Scratch.Add "index" (dict "uri" .Permalink "title" .Title "content" .Plain) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

This will create a JSON file at the root of your site (index.json) that includes the URL, title, and content of each page.

Step 4: Implementing the Search Functionality

Finally, you’ll need to add some JavaScript code to load the search index and perform searches when the user types into the search box. You can add the following code to your layouts/partials/footer.html file or layouts/partials/scripts.html file:

 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
<script>
var indexData = [];

fetch('/index.json')
.then(response => response.json())
.then(data => {
    indexData = data;

    const options = {
        includeScore: true,
        keys: ['title', 'content']
    };
    
    const fuse = new Fuse(indexData, options);
    
    document.getElementById('search').addEventListener('input', function() {
        let results = fuse.search(this.value);
        let searchResultsHTML = '';
        
        results.forEach(function(result) {
            let item = result.item;
            searchResultsHTML += '<li><a href="' + item.uri + '">' + item.title + '</a></li>';
        });
    
        document.getElementById('searchResults').innerHTML = searchResultsHTML;
    });
});
</script>

In this code, indexData is the search index data that was loaded from index.json. The fetch('/index.json') fetches the data from the index.json and fuse.search(this.value) is the function that performs the search whenever the user types into the search box.

Note: Fetch API needs a server to work. If you try to use the fetch function with the file:// protocol, it will not work. You can use hugo server to run your website locally for testing.

Displaying Search Results

To display search results, you’ll want to create a container in your HTML where the results can be displayed. This could be a simple unordered list. The JavaScript code can then dynamically populate this list with the search results.

Follow these steps:

  1. Add a search results container to your layout: This could be a simple unordered list beneath your search box. Here’s how you might modify the navbar in your layout:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    <div class="navbar-item">
        <div class="field has-addons">
            <div class="control">
                <input id="search" class="input" type="text" placeholder="Search">
            </div>
            <div class="control">
                <button id="searchBtn" class="button is-info">
                    Search
                </button>
            </div>
        </div>
        <!-- Add this ul to your layout -->
        <ul id="search-results"></ul>
    </div>
    
  2. Modify your JavaScript code: You need to modify the oninput event handler in your JavaScript code to update the search results container whenever the user types into the search box. Here’s how you might do it:

     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
    
    window.onload = function () {
      fetch('/index.json')
      .then(response => response.json())
      .then(data => {
        const fuse = new Fuse(data, {
          keys: ['title', 'content'],
          includeScore: true
        });
    
        const searchBox = document.getElementById('search');
        const resultsContainer = document.getElementById('search-results');
    
        searchBox.oninput = function () {
          // Clear previous results
          resultsContainer.innerHTML = '';
    
          const results = fuse.search(searchBox.value);
    
          // Add new results to the results container
          for (let result of results) {
            const li = document.createElement('li');
            const a = document.createElement('a');
            a.href = result.item.permalink;
            a.textContent = result.item.title;
            li.appendChild(a);
            resultsContainer.appendChild(li);
          }
        };
      });
    }
    

    This code first clears the search results container whenever the user types into the search box. It then populates the container with the new search results. Each result is a list item containing a link to the post.

With these changes, your search box should dynamically display search results as the user types into it. Each result is a link to a post, and the results update in real time as the user types.

If you want to display search results on the page instead of the navigation bar, you’ll need to move the <ul id="search-results"></ul> element to a location outside of the navigation bar.

Here’s a sample of how you might structure your baseof.html file to include a search results container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
    {{ partial "header.html" . }}
    <!-- Search Results Container -->
    <section id="search-results-section">
      <ul id="search-results"></ul>
    </section>
    <!-- Main Content -->
    <section class="section">
      <div class="container">
        {{ block "main" . }}{{ end }}
      </div>
    </section>
    {{ partial "footer.html" . }}
</body>
</html>

In this layout, the search results container (#search-results-section) is separate from the navigation bar and the main content. The search results will be displayed in this container when the user types into the search box.

You may also want to update the JavaScript code to clear the main content when displaying search results. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
searchBox.oninput = function () {
  // Clear previous results
  resultsContainer.innerHTML = '';

  // Clear main content
  const mainContent = document.querySelector('.container');
  mainContent.innerHTML = '';

  const results = fuse.search(searchBox.value);

  // Add new results to the results container
  for (let result of results) {
    const li = document.createElement('li');
    const a = document.createElement('a');
    a.href = result.item.permalink;
    a.textContent = result.item.title;
    li.appendChild(a);
    resultsContainer.appendChild(li);
  }
};

This updated JavaScript code will clear both the previous search results and the main content when the user types into the search box. This way, only the search results will be displayed on the page.

Using Bulma with Hugo

Let’s walk through the process of building a simple Hugo site with Bulma CSS from scratch.

Step 1: Create a New Hugo Site

We’ll start by creating a new Hugo site:

1
2
hugo new site my-blog
cd my-blog

Step 2: Add Bulma CSS to Your Site

Download Bulma CSS into your static directory:

1
2
mkdir static/css
curl https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/css/bulma.min.css -o static/css/bulma.min.css

Then, in your layouts/partials directory, create a head.html file and include the Bulma CSS:

1
2
3
4
5
6
7
<!-- layouts/partials/head.html -->

<head>
    <meta charset="utf-8">
    <title>{{ .Title }}</title>
    <link rel="stylesheet" href="/css/bulma.min.css">
</head>

Step 3: Create a Layout for Your Site

In the layouts/_default directory, create a baseof.html file that defines the basic layout for your site:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- layouts/_default/baseof.html -->

<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
    {{ block "header" . }}{{ end }}
    {{ block "main" . }}{{ end }}
    {{ block "footer" . }}{{ end }}
</body>
</html>

Here, the block directives are placeholders that can be filled in by other templates.

Step 4: Create a Header with a Navigation Bar

Create a header.html file in your layouts/partials directory that defines the header block:

 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
<!-- layouts/partials/header.html -->

{{ define "header" }}
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
        <a class="navbar-item" href="{{ "/" | relURL }}">
            Home
        </a>
    </div>

    <div class="navbar-menu">
        <div class="navbar-start">
            <a class="navbar-item" href="{{ "/posts" | relURL }}">
                Articles
            </a>
        </div>
        
        <div class="navbar-end">
            <div class="navbar-item">
                <div class="field has-addons">
                    <div class="control">
                        <input class="input" type="text" placeholder="Search">
                    </div>
                    <div class="control">
                        <button class="button is-info">
                            Search
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</nav>
{{ end }}

This creates a navigation bar with Home, Articles links, and a search box.

Step 5: Create a Layout for the List of Posts

Next, we’ll create a list layout for displaying the list of posts. In the layouts/posts directory, create a list.html file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- layouts/posts/list.html -->

{{ define "main" }}
<section class="section">
    <div class="container">
        <div class="content">
            {{ range .Pages }}
            <h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
            {{ end }}
        </div>
    </div>
</section>
{{ end }}

This lists the titles of all posts as links.

Step 6: Create a Layout for Individual Posts

Finally, we’ll create a layout for individual posts. In the layouts/posts directory, create a single.html file:

html
<!-- layouts/posts/single.html -->

{{ define "main" }}
<section class="section">
    <div class="container">
        <div class="content">
            <h1>{{ .Title }}</h1>
            {{ .Content }}
        </div>
    </div>
</section>
{{ end }}

This displays the title and content of an individual post.

You can now create some posts in the content/posts directory and view your site by running hugo server. The search box does not work yet - we previously discussed how to add search functionality using JavaScript libraries like Fuse.js or Lunr.js.

Remember that this is a very basic setup and you might need to add more styles or functionality to suit your needs. You can consult the Hugo documentation and the Bulma documentation for more information.

You can use the Bulma CSS classes to style and position your search results. You can wrap your search results list in a container class to center it and provide a maximum width. Then, you can further wrap it in a section class to provide some vertical spacing.

Here’s how you can adjust your baseof.html file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
{{ partial "head.html" . }}
<body>
    {{ partial "header.html" . }}
    <!-- Search Results Container -->
    <section class="section">
      <div class="container">
        <ul id="search-results"></ul>
      </div>
    </section>
    <!-- Main Content -->
    <section class="section">
      <div class="container">
        {{ block "main" . }}{{ end }}
      </div>
    </section>
    {{ partial "footer.html" . }}
</body>
</html>

This will make sure that your search results are properly contained and centered, with a comfortable amount of vertical spacing. Remember to clear the search results container whenever a new search is conducted or when the search input is cleared.

If the positioning still doesn’t look right, it’s likely that some additional custom CSS will be needed. Bulma is a very flexible framework, but there are times when the built-in classes might not provide the exact positioning or styling you’re looking for. In those cases, you can write some custom CSS to tweak the layout to your liking.

Code Syntax Highlighting

Hugo has built-in support for syntax highlighting through the use of Chroma. Here’s how you can enable it:

  1. First, add the following lines to your site’s configuration file (for example, config.toml) to enable syntax highlighting:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    [markup]
      [markup.highlight]
        codeFences = true
        guessSyntax = false
        highlightInClient = false
        lineNos = true
        lineNumbersInTable = true
        noClasses = true
        style = "monokai"
        tabWidth = 4
    

    You can adjust these options according to your needs. The style option determines the color scheme of the highlighted code. “monokai” is just one example, you can choose another style from Chroma’s style gallery.

  2. Then, you can include code in your markdown files using code fences, and Hugo will automatically highlight it. Here’s an example:

    1
    2
    3
    
    ```python
    def hello_world():
        print("Hello, world!")
    
    
    Make sure to replace "python" with the appropriate language identifier for your code.
    

Note: If you set noClasses = false in the configuration, then Hugo will use CSS classes to style the code. You will need to include the appropriate CSS on your site. You can generate this CSS using the hugo gen chromastyles --style=monokai > static/css/syntax.css command and then include it in your head.html partial:

1
<link rel="stylesheet" href="/css/syntax.css">

The public Folder

Can I delete everything in the public folder to force hugo to regenerate the files to pickup the changes?

Yes, you can safely delete everything in the public folder. The public directory is where Hugo generates the static website files, and it’s safe to delete because Hugo will regenerate these files every time you run the server or build the site.

To delete the public folder, you can use this command in your terminal:

1
rm -rf public/*

Then, you can run Hugo again to regenerate the site:

1
hugo server

After running this command, your public directory should be filled with new, up-to-date files generated from your content and layout files.

In general, if you’re running the hugo server command for local development, Hugo should automatically regenerate the site when it detects changes in your files. However, manually deleting the public folder can be useful in some cases if you want to be sure you’re starting from a clean state.

The content Folder

Do I need to put the md files inside content/posts folder ?

Yes, according to the structure and configurations we have discussed, your Markdown (.md) files representing your blog posts should be placed inside the content/posts/ directory.

Here is the reasoning:

In Hugo, the content organization directly reflects your URL structure. If you place an .md file directly under the content/ directory, it will be treated as a standalone page. On the other hand, if you place it under content/posts/, it’s considered a part of the “posts” section. The “posts” section is what we’ve set up to display on the home page as a list of all articles.

So, for each of your blog posts, create a new Markdown file inside the content/posts/ directory. Each file should start with a front matter section, where you specify metadata like the title and date. Here’s an example:

1
2
3
4
5
6
7
---
title: "My First Post"
date: 2023-06-07
draft: false
---

Here's the content of my first post...

After creating your posts, run your Hugo server again, and you should see the titles of all your posts listed on the home page. Clicking a title will take you to the respective post’s page.

The home Page

You can add static text by creating a custom homepage layout. Hugo allows you to override the list page layout for the homepage.

Follow these steps to create a custom homepage layout:

  1. In the layouts directory, create a new file called index.html. This will serve as the layout for your homepage.

  2. Open index.html and enter the following content:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    {{ define "main" }}
    <div class="content">
        <h1>Welcome to my blog</h1>
        <p>Some interesting static text here...</p>
        <h2>Articles:</h2>
        {{ range .Site.RegularPages }}
        <h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
        {{ end }}
    </div>
    {{ end }}
    

    This layout first displays a static welcome message, followed by a list of all your articles.

  3. Save your index.html file.

With this setup, when you visit your homepage, you will see a welcome message followed by a list of all your articles. This is because Hugo uses the index.html layout for the homepage when it exists.

The range .Site.RegularPages line is a loop that generates a list of all your pages across all sections (in your case, the articles in the “posts” section).

Don’t forget to restart your Hugo server after making these changes, so that the new layout is used.

Deploying Hugo to AWS Amplify

I did not have any luck running the build and get the Go runtime working on Netlify, Vercel or AWS Amplify. I generate the files for production by running:

1
HUGO_ENV=production hugo --minify

and deploy all the files under the static folder to the blog.

What should be the baseURL for development and production in toml file?

In Hugo, the baseURL parameter in the configuration file is the URL on which your site will be built.

For local development, the baseURL can typically be set to the root ("/") or to “http://localhost:1313”, which is the default server address when running Hugo server locally.

For example:

1
baseURL = "http://localhost:1313"

For a production build, you should set baseURL to the domain name or IP address where your site will be hosted.

For example, if your website domain is www.example.com:

1
baseURL = "http://www.example.com/"

If your Hugo project needs to be built differently for different environments (such as local development vs production), you can use Hugo’s environment feature.

You can define the environment when building your site with the HUGO_ENV environment variable. Then you can use the getenv function in your config file to change the baseURL based on the environment:

1
2
3
4
5
[production]
    baseURL = "http://www.example.com/"

[development]
    baseURL = "http://localhost:1313"

When running Hugo, specify the environment:

1
2
HUGO_ENV=production hugo --minify  # for a production build
HUGO_ENV=development hugo server    # for local development

Hugo will use the corresponding baseURL based on the HUGO_ENV variable.