Create Static Website with Hugo
This site is a from-scratch Hugo theme built around a docs-style shell and a custom color system. This post is the setup notes: installation, how I actually write posts day-to-day, how the theme’s files are organized, and the color palette behind it.
1. Installation
brew install hugo
Get the extended edition. brew install hugo on macOS gives you this by default, but on other platforms make sure you’re not on the plain build. Extended is required for the Sass/CSS asset pipeline (css.Build, js.Build via esbuild) that this theme relies on.
hugo version
# hugo v0.164.0+extended
Scaffold a new site and a new theme inside it:
hugo new site quickstart # Replace quickstart to your blog name.
cd quickstart
hugo new theme evershibui # Replace evershibui to your custom theme name.
Set the theme in hugo.toml:
theme = 'evershibui'
Note
hugo new theme scaffolds using modern Hugo conventions: templates live flat in layouts/ (baseof.html, home.html, page.html, no _default/ folder), and partials live under layouts/_partials/. Older tutorials and themes you find online often use the pre-0.146 layout (layouts/_default/baseof.html, layouts/partials/). Both still work, but don’t mix conventions inside one theme.
Delete the scaffold’s placeholder demo content (themes/<name>/content/) if you don’t want fake sample posts leaking into your site. Hugo merges a theme’s content/ into the site’s own.
2. Obsidian workflow setup
This is a step by step guide to setup obsidian as a main editor to write posts.
Here is a checklist:
-
.obsidianappears in.gitignore -
content/posts/Templates/appears in.gitignore -
ignoreFiles = ['/posts/Templates/']is set inconfig/_default/hugo.toml
Obsidian Vault
- Open a vault inside
content/posts/and set.obsidianfolder to.gitignore. - Set Obsidian’s default attachment location is set to “Same folder as current file”, so pasted images land straight in the right post bundle.
Template
- Create a
Templates/folder insidecontent/posts/and add it to.gitignoreand to Hugo’signoreFiles, so it never shows up as a post. This folder is gitignored, so recreate it by hand on a fresh clone. - Inside it, create
Post Template.mdwith the settings below. Obsidian’s Core Templates plugin fills in the current date and time for you once you apply the template to a note.
---
date: {{date:YYYY-MM-DD}}T{{time:HH:mm:ss}}+08:00
draft: true
title:
categories:
tags:
description:
math: false
---
Tag convention: Obsidian tags can’t contain spaces. This site uses kebab-case instead.
An example:
tags: ['linear-algebra', 'math']
Creating a new post: in Obsidian, create a new note and name it my-new-post/index. The slash makes Obsidian create the my-new-post/ folder automatically. Then apply the template to that index.md.
This ignore setting also matters if you create posts with hugo new posts/<slug>/index.md from the terminal. Without it, Hugo would try to build the Templates/ folder as a page.
There are two scaffolds to keep aligned by hand:
archetypes/default.md | Templates/Post Template.md | |
|---|---|---|
| Used by | hugo new posts/<slug>/index.md (terminal) | Obsidian’s template-apply command |
| Fires | Once, at file creation via CLI | Whenever you manually apply/insert it in Obsidian |
Front matter format: Obsidian’s Properties panel only reads and writes YAML front matter (---), never TOML (+++). Hugo supports both natively and auto-detects the delimiter per file, so the fix is on the Hugo side. Every post uses YAML, and the theme’s archetype (archetypes/default.md) was updated to generate YAML too. New notes created from Obsidian or via hugo new stay consistent:
---
date: '{{ .Date }}'
draft: true
title: '{{ replace .File.ContentBaseName "-" " " | title }}'
---
Attachments
One caveat worth knowing: Obsidian-only syntax ([[wikilinks]], ![[embeds]]) doesn’t mean anything to Hugo’s Markdown renderer, so links from a post into other (unpublished) vault notes just render as broken literal text. Practice is to inline the relevant content into the post itself rather than link out of blog/.
Images: page bundles, created from the start. Every post with images is created as a leaf bundle, not a flat file:
hugo new posts/my-new-post/index.md
This gives a my-new-post/ folder with index.md inside it, generated from archetypes/default.md. From there:
- Images get dropped straight into that same folder and referenced by filename, like
. - No separate
static/images/<slug>/tree with absolute paths. - Post and assets stay together as one movable unit.
- Obsidian’s “default location for new attachments” is set to “Same folder as current file,” so screenshots pasted while editing
index.mdland next to it automatically. No manual sorting after the fact.
Note
A post started the old way (flat <slug>.md plus static/images/<slug>/) still works, but has to be converted by hand:
mkdirthe bundle folder.git mvthe.mdin asindex.md.git mveach image in alongside it.- Strip the
/images/<slug>/prefix from the Markdown image links so they resolve as bundle-relative filenames.
3. Deploying to GitHub Pages
Publish
content/posts/ is a real, git-tracked directory, the same folder Obsidian edits directly. Publishing a post is just a normal commit:
git status content/posts # review what changed
git add content/posts
git commit -m "..."
git push
Deploy
Push to main triggers .github/workflows/hugo.yaml (Hugo’s official Actions workflow), which builds with hugo build --gc --minify and deploys via actions/deploy-pages. Pages’ source is set to GitHub Actions (Settings → Pages → Source), not a branch, so there’s no gh-pages branch to manage.
Tip
After a deploy, GitHub’s edge CDN refreshes almost immediately (cache-control: max-age=600, so worst case ~10 minutes). But the browser may still show a stale cached copy well after that. Don’t wait it out, hard refresh instead:
- macOS Chrome/Firefox/Edge:
Cmd+Shift+R - macOS Safari:
Cmd+Option+R(or Develop → Empty Caches) - Or just open the URL in a private/incognito window