We're transitioning Studio from Beta to Early Availability

How to build a static site with Hugo

Let's learn how to build a static site with Hugo, covering everything from installation to publishing your site.
Joel Olawanle

Written by Joel Olawanle

Mar 03, 2023
How to build a static site with Hugo

Developers are constantly looking for new, more efficient ways to create websites. You want to build websites at a blazing-fast speed that pulls content quickly. This is why static websites exist. Static websites are popular due to their simplicity, security, and scalability.

In this article, you will learn what a static website means and how it differs from dynamic websites. You will also learn how to build a static site with Hugo, covering everything from installation to publishing your site. Finally, in this article, you will learn how to fetch data from external sources like a GraphQL API into your static site to add interactivity.

overview of the static website built in hugo

Here is a live link to test the example static site you will learn how to build in this guide.

#What is a static site?

A static site is a website built with pages of static content or plain HTML, JavaScript, or CSS code. Its content is served to the user exactly as stored, without being dynamically generated and served to the user via a web server, without any server-side processing or database interaction.

Static websites offer little interactivity as they aim to show the same content to all users. This content can be pre-rendered, leading to fast performance and lightweight overhead in simple use cases. Simple static sites are a good option for sites where the content stays mostly the same and when the sites are simple.

When you need to implement some logical use cases, you can use Static Site Generators (SSGs), which gives teams more power and flexibility with their project. SSG has several advantages like fast loading times and no server-side processing when compared to Single Page Applications (SPA) and Server-side Rendered Apps (SSR)

#What is Hugo?

Hugo is an open-source static site generator written in Go. It generates HTML pages from source content written in markdown and can be used to build websites, blogs, and other types of web content. Hugo is fast, flexible, and easy to use, making it a popular choice for building static websites.

Hugo is our top SSG pick, along with Jekyll and Next.js, which works perfectly with any Headless CMS like Hygraph to pull content rendered in HTML. It is known for its fast build times, ease of use, and large library of customizable themes. You can quickly create and launch a fully-functional website using Hugo without complex back-end systems. Whether you're a beginner or an experienced developer, Hugo offers a flexible and efficient solution for building a static site.

#How to install Hugo

Hugo can be installed on all devices, such as macOs, windows, and Linux, in multiple ways as laid out in the documentation with a package manager.

How to install Hugo on macOS and Linux

Installing Hugo on macOS and Linux is straightforward, as you can use the Homebrew package manager. If you don’t already have Homebrew installed, you can install it by running the command below in the terminal:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Once Homebrew has been installed successfully, you can run the following command in your terminal to install Hugo:

brew install hugo

How to install Hugo on Windows

Installing Hugo on windows is quite complex, as you’d need to install the Chocolatey or Scoop package managers. You can check out the Chocolatey and Scoop documentation for better instructions on how to install them.

Once you are done installing either Chocolatey or Scoop, you can install Hugo using one of the following commands depending on your installed package manager:

choco install hugo-extended -confirm
scoop install hugo-extended

How to verify that Hugo is installed correctly

At this point, you have successfully installed Hugo but may want to confirm if it has been installed, or you may want to confirm the installed version. You can verify by running the following command in your terminal:

hugo version

This command should output information regarding the currently installed version of Hugo, as seen below:

hugo v0.85.0+extended darwin/arm64 BuildDate=unknown

#How to create a Hugo site

You now have Hugo installed on your computer locally. The next step would be to create a Hugo site for you to start work. This is quite easy to do, as all you have to do is make use of the following command in your terminal:

hugo new site <hugo-site-name> -f yml

Ensure you replace <hugo-site-name> with the name of your site, such as “hugo-portfolio-site”. A new site will be created within a few seconds with instructions on your terminal.

setting up hugo

You can now navigate to the new project directory to access its files using cd <project-name> to change the directory. Once that is done, you can open your project in your preferred IDE. For me, I will use VSCode via the command code ..

Understanding the Hugo folder directory

Hugo's specific file structure makes it easy to organize content and manage the website. The folder structure will look like this:

├── archetypes/
│ └── default.md
├── assets/
├── content/
├── data/
├── layouts/
├── public/
├── static/
├── themes/
└── config.yml

Following this structure, Hugo makes creating, managing, and maintaining a website easy. The content and presentation are separated, making it simple to change the look and feel of the site without affecting the content. The main components of a Hugo site are:


This is used to define the default templates for new content files, making it easier to create new content. You can have different content formats in this folder, but by default, there is one for posts with the file name defualt.md. This means when you want to create a new posts markdown file, you can use the following command, and a new file will be created and pre-populated with the specified front matter.

hugo new posts/my-first-post.md

If you prefer to pre-populate your generated Markdown files, you can adjust the file to contain more front matter.


The "assets" folder in Hugo refers to the directory where you can store static assets such as images, stylesheets, scripts, and other files used by your website. These files are then processed and copied to the final public directory during the build process.

By organizing your assets in an "assets" folder, you can keep your project structure organized and separate your assets from your content and templates. This can also make it easier to manage and update your assets in the future. Overall, the "assets" folder in Hugo is an important part of the build process and is used to manage and optimize the static assets used by your website.


The “content” folder is where all the markdown files, images, and other media files are stored. It refers to the directory where you store your site's content, such as blog posts, pages, and other pieces of content that will be displayed on your website. This folder is an essential part of the Hugo build process, where you'll spend most of your time creating and editing your site's content.

In Hugo, the content in the "content" folder is processed and transformed into HTML pages during the build process. The resulting HTML pages are then stored in the public directory, which is the directory that will be served to your site's visitors.


The "data" folder in Hugo refers to the directory where you store data files that are used to populate your site's templates. This data can include site configuration, author information, product details, and any other data that needs to be referenced in your templates. Using data files, you can keep your data organized and separate from your content and templates, making it easier to manage and maintain your site.


This is where the HTML templates that define how the content is presented on the site are stored. These templates define the HTML generated for each page on your site and determine how your content will be displayed to your visitors. These templates include list pages, your homepage, taxonomy templates, partials, single-page templates, and more. When you use a theme, you may not need to create a template except edit the theme.


The "public" folder in Hugo refers to the directory where the static files that make up your website are generated and stored. This folder contains the HTML, CSS, JavaScript, images, and other files that make up your site, and it is the directory that will be served to your visitors when they visit your site.

The "public" folder is generated every time you run the Hugo build process and is overwritten each time you make changes to your site's content, data, or templates.


The "static" folder refers to the directory where you store static assets that Hugo does not process during the build process. This folder stores files such as images, JavaScript, CSS, and other files that do not need to be processed or modified in any way by Hugo.

You can reference files in your templates' "static" folder in Hugo using the {{.Site.BaseURL }} variable and the file path, for example, {{.Site.BaseURL }}/images/logo.png}}. This allows you to include static assets in your site's templates, making it easy to include images, scripts, and other files.


The "themes" folder in Hugo refers to the directory where you store custom or pre-made themes that define the look and feel of your website. A theme in Hugo is a collection of templates, styles, and assets that determine the design of your site.

The "themes" folder allows you to store multiple themes that you can easily switch between without affecting your site’s content and data. You can maintain a clean and organized project structure by keeping your themes in a separate directory.


The config.yml or config.toml file in Hugo is the main configuration file for your Hugo project. This file contains the settings and options that define the behaviour of your site, including the title, base URL, theme, content and data directories, and other important settings. Depending on the theme, you can also configure more options. All these options are well documented in the theme’s documentation.

#How to install and configure a theme in Hugo

Hugo has hundreds of official and community-created themes available on their official website. These themes range from simple and minimalist designs to complex and feature-rich themes with advanced features and customization options. Constantly new themes are added, and existing themes are updated.

For this guide, you will use the PaperMod theme, a fast, clean and responsive theme with many features. There are two major ways to install this theme. You can first decide to download the theme and copy it into your themes folder, or you can use your terminal by cloning the GitHub repository into your themes folder using the command below.

git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1

You can learn more about the installation here. When you have successfully installed the theme, you will notice a new folder with the theme name (PaperMod) in the themes folder of your project. The next step would be to configure the theme.

To configure your theme in Hugo, you will add the theme name to the config.toml or config.yml file in your Hugo site.

baseURL = 'http://example.org/'
languageCode = 'en-us'
title = 'My New Hugo Site'
theme = 'PaperMod'

By default, you will notice the three configurations for baseURL, languageCode and site title. You can edit them based on what you want, but you can now remove the baseURL pending when you deploy your website. At this point, you can now test your site to see if it reflects the installed theme by running the following command:

hugo server

This will serve your site locally, which you can now access at http://localhost:1313/:

initial preview of the hugo site

#How to add content in Hugo

Adding content to Hugo is quite easy as all you have to do is populate the content folder with markdown contents, and they will immediately appear on your website. The PaperMod theme is built to display the recent blog posts on the home page, as there is no direct index page (later in this article, you will create the index/home page). There is also a blog page that shows a list of all posts.

There are two standard ways to add content to your Hugo site. You either add the content manually by creating a posts folder and then creating markdown files in the posts folder or using the terminal. For this guide, let us use the terminal by running the following command.

hugo new posts/<title-of-article>.md

The file name serves as the slug for the post. Feel free to add markdown content, or you can check out my final projects to copy my markdown content into your project.

At this point, your website will look like this:

displaying blog posts in hugo

#How to configure PaperMod Hugo theme

The PaperMod theme has some parameters you can configure to make your website attractive, easy to navigate and hold more information. For example, you will love to enable some home parameters to display information about you with some of your social links. You will also want to add some navigation links to make it easier for users to navigate your website.

configuring the papermod theme

All these and more can be configured in the config.yml or config.toml file. Below is an example of the config used for the demo project. If you paste this into your config.yml file (because this is in yml format), you will get a similar result as the picture above. You can then edit the title, homeInfoParams data’s and set up your preferred menu items with the path or external links.

baseURL: ''
languageCode: en-us
title: Joel's Portfolio
theme: PaperMod
Title: "Hey, I’m Joel \U0001F44B"
Content: I am a Front-end Engineer & Technical Writer. I enjoy creating interactive solutions with JavaScript. On my blog, I write about code (mostly JavaScript and React) and everything I learn. I’m actively exploring the world of web3 and blockchain technology.
- name: twitter
url: https://twitter.com/joelolawanle
- name: github
url: https://github.com/olawanlejoel
- name: linkedin
url: https://www.linkedin.com/in/joelolawanle/
- identifier: home
name: Home
url: /
weight: 10
- identifier: posts
name: Blog
url: /posts/
weight: 20
- identifier: contents
name: Contents
url: https://joelolawanle.com/contents
weight: 30

You can learn more and see more configuration settings that work with the PaperMod theme in the documentation.

#How to customize a Hugo theme

For this article, you want the home page to hold only information about you and some of your projects which will be fetched from Hygraph. This means you must create an index page (home page) and customize it to work as you desire.

When making changes to the theme, it is best not to edit it directly because you may need to update the theme when there is an update. To customise a theme’s HTML or CSS files, you will have to re-create the same file with similar directly in your original project’s directory. This means if you want to edit a CSS file (main.css) in the assets folder of your theme, you will need to create the same file in your project’s assets folder with a similar file directory.

For this guide, I customized the Hugo PaperMod theme by creating an index.html file in the layouts folder to override what shows on the home page and implement my desired output. You can check out this GitHub repository to check all the changes done in the layout folder and the CSS styles added in the assets folder of the original project.

In the index.html file, you are first using templating to get the home info parameters declared in the config.yml file into the index.html file and then a projects section is created with a single card that is meant to display each project when you fetch them from Hygraph.

{{- define "main" }}
{{- $pages := union .RegularPages .Sections }}
{{- $paginator := .Paginate $pages }}
{{- if and .IsHome site.Params.homeInfoParams (eq $paginator.PageNumber 1) }}
{{- partial "home_info.html" . }}
{{- end }}
<div class="projects-section">
<div class="portfolio-projects">
<div class="project">
<div class="project-header">
<i class="fa-regular fa-folder-open folder-icon"></i>
<div class="small-icons">
<a href="#"><i class="fa-brands fa-github"></i></a>
<a href="#"><i class="fa-solid fa-up-right-from-square"></i></a>
<h3>Project Title</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Maiores soluta, incidunt sed non animi voluptates quo! Dolorum, atque. Rem soluta sunt similique labore officia, illo voluptatum alias quo tempora sequi!</p>
{{- end }}

When you run hugo server in your terminal, your project will look like this:

current state of your static site in hugo

#How to pull content from a CMS

You have successfully installed a theme, customized the theme and added a home page. What’s left is to fetch content from an external API into your project. For this guide, I have created a content repository on Hygraph to hold all my project's titles, descriptions, and Live and GitHub links. This is what the schema of the Hygraph project’s content repository looks like:

your content schema in hygraph

You can create a similar or preferred content repository if you don’t want to use your data.

Hygraph is a headless content management platform that enables teams to distribute content to any channel. If this is your first time learning about Hygraph, establish a free-forever developer account. Hygraph offers you an API that you can use to fetch your content with GraphQL. Hugo is an SSG that works with HTML, CSS and JavaScript, but you can perform dynamic operations and pull dynamic data using Go's templating language.

Templating in Hugo

In Hugo, dynamic data can add dynamic content to your templates. Dynamic data can include information such as the date and time, the title and content of your pages, and information stored in data files or fetched from external APIs.

Hugo uses Go's templating language to render templates and dynamic data. In your templates, you can access dynamic data using placeholders, which are enclosed in double curly braces. For example, {{ .Title }} can be used to access the current page's title.

Using dynamic data in your templates, you can create dynamic and interactive websites that can be easily updated and maintained. Whether displaying the latest blog post, showing a list of products, or providing up-to-date information, dynamic data can help you create dynamic and engaging websites with Hugo.

How to fetch data from Hygraph with resources.GetRemote Hugo pipe

In Hugo, the "resources.GetRemote" function is part of the "resources" package and is used to fetch remote resources, such as images or other files, and create a "Resource" object that can be processed and manipulated within your Hugo templates.

The GetRemote function can take in two arguments, the first is compulsory, which is the URL you want to extract information from, while the second is an optional argument which can be options. For this guide, you will be fetching data from a GraphQL API, which means you need to send a POST request, specify the content type and create a GraphQL query in the body just like it’s done with the FetchAPI:

{{ $options := dict
"method" "POST"
"headers" (dict "Content-Type" "application/graphql")
"body" `{
projects {

At this point, you have stored the options in a $option variable which would be added as the second argument for the GetRemote function:

{{ with resources.GetRemote "https://api-eu-west-2.hygraph.com/v2/cldhdnrg318on01taclc7ch48/master" $options }}
{{ with .Content }}
{{ with transform.Unmarshal . }}
{{ with .data.projects }}
{{ range . }}
<!-- TEsting data -->
{{ .title }}
{{ end }}
{{ end }}

In the above code, you are using the resources.GetRemote function to request a resource, then check if the content has been fetched before transforming it to readable data. You only output the title using {{ .title }} based on how the data identifier is named on Hygraph.

You can now wrap this around the product card to loop through and use the templates to add each data appropriately:

<div class="projects-section">
<div class="portfolio-projects">
{{ with resources.GetRemote "https://api-eu-west-2.hygraph.com/v2/cldhdnrg318on01taclc7ch48/master" $options }}
{{ with .Content }}
{{ with transform.Unmarshal . }}
{{ with .data.projects }}
{{ range . }}
<div class="project">
<div class="project-header">
<i class="fa-regular fa-folder-open folder-icon"></i>
<div class="small-icons">
<a href={{ .gitHubLink }}><i class="fa-brands fa-github"></i></a>
<a href={{ .liveLink }}><i class="fa-solid fa-up-right-from-square"></i></a>
<h3>{{ .title }}</h3>
<p>{{ .description }}</p>
{{ end }}
{{ end }}

I have only few projects added. All the projects will be fetched into my Hugo site and displayed just like this:

displaying project stored in the cms in hugo

So far, in this guide, you have learned how to build a static site with Hugo and customize the theme to work like an actual portfolio. The last step would be to implement interactivity. This is because by default Hugo caches the resources fetched using the resources.GetRemote function to improve the performance of your site.

Caching the resources helps reduce the time and bandwidth required to fetch the resources, improving your site's speed and responsiveness. Once a resource is fetched and processed using resources.GetRemote, it is stored in Hugo's cache, located in the resources directory at the root of your Hugo site. The next time the resource is needed, Hugo will check the cache before making another request to the remote server. If the resource is found in the cache, Hugo will use the cached version instead of making another request, which can significantly improve your site's performance.

But there is an issue with the caching. Suppose you update the data in your Hygraph content repository; it will not update because Hugo does not check the cached content to see if they match but will only check if such a request exists. To ensure your data updates when you refresh your static site, you can deploy your project to Vercel, create a webhook and configure it with Hygraph so that the project re-builds every time you publish/unpublish a project.

Deploy to Vercel with Hygraph webhooks

The first step would be to push your project to GitHub. Pushing a Hugo project that has a theme is quite logical as you need to create a .submodules file to hold details about the theme, as seen below:

[submodule "themes/PaperMod"]
path = themes/PaperMod
url = "https://github.com/adityatelange/hugo-PaperMod.git"

Once this is done, you can create a GitHub project and push your project easily using the Git commands. On GitHub, your project will look like this:

project structure on github

You can now deploy your project to Vercel by importing from GitHub and then setting all the build commands alongside an environment variable of the “HUGO_VERSION” with the version as the value such as “0.110.0”.

setting up the github repo on vercel

Once you have successfully deployed your Hugo site, you can now setup/create a deployment hook with Vercel by navigating to the project Git settings, scroll to the "Deploy Hooks" section. You'll then specify the name of your hook, and the Git branch name you want to trigger a deployment.

setting up webhooks in vercel

Once you've configured your hook, create it, and copy the URL. The URL will be used as a webhook within your Hygraph project. In the project dashboard, click webhooks and then create a new webhook. Give your webhook a name, and paste your Vercel deploy hook URL inside the URL input. Also ensure you configure the Triggers with the content model and stage that should trigger a re-deploy.

setting up webhooks in hygraph

Once you have successfully created the hook, everything will work well, and your Hugo site will be rebuilt every time you publish a new entry. You can learn more about how to deploy your Vercel project with Hygraph webhooks in this documentation.

At this point, you have successfully pulled content from Hygraph content management system into your Hugo site and also added interactivity, so your data is always up to date. It’s important o know that you can also build your Hugo site using the command below:


This will build your Hugo site and generate the HTML, CSS, and other files in the public directory. The generated files can then be deployed to a web server or hosting platform, such as GitHub Pages, Netlify, or AWS S3, to make your site accessible to the public. You can read more about hosting and deployment in Hugo in this documentation.

#Moving forward with Hugo and SSG

In this guide, you have learned what static sites mean and how you can use Hugo (one of the leading SSG) to build static sites and provide your team with a fast, reliable and easy-to-maintain website.

Hygraph is completely compatible with Hugo and other popular SSGs. To move forward with Hugo and static site generation, you need to familiarize yourself with Hugo's documentation and tutorials, experiment with different themes, use Hugo's built-in shortcodes, explore Hugo's add-ons, and many more.

By following these steps and continuing to learn about Hugo and static site generation, you can build a powerful and effective website that meets your needs and provides a great user experience. Find out more in our ebook about how Static Site Generators and Headless CMS work hand-in-hand.

Have fun coding and learning!

Blog Author

Joel Olawanle

Joel Olawanle

Joel Olawanle is a Frontend Engineer and Technical writer based in Nigeria who is interested in making the web accessible to everyone by always looking for ways to give back to the tech community. He has a love for community building and open source.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.