Migrating from Hexo to Hugo: A Two-Day Journey with a Custom Theme
Introduction
After spending two days migrating my blog from Hexo to Hugo, I’m excited to share my journey. The move wasn’t just about switching tools—it was about creating something tailored to my needs. With AI assistance, I designed a custom Hugo theme called Signal that reflects my style and requirements.
This post covers the migration process, highlights the key differences between Hexo and Hugo, and introduces the Signal theme along with the GitHub Actions deployment workflow.
The Signal Theme
The Signal theme is a modern Hugo blog theme built specifically for technical writers and engineers. It features:
- Dark mode with system-preference detection and manual toggle
- Inline search with keyboard shortcut support
- Reading progress bar and back-to-top button
- Giscus comments integration (fully configurable)
- Social sharing buttons for Twitter, LinkedIn, Facebook, and copy-link
- Author bio section with avatar and social links
- Related posts based on tag matching
- Responsive design with mobile-first approach
- Schema.org structured data for SEO
- Print styles and reduced-motion support
The theme is published as an open-source project, available via Hugo modules, submodules, or direct clone.
GitHub Actions Deployment Workflow
The deployment uses GitHub Actions to automatically build and deploy the site on every push to the main branch:
name: Deploy Hugo site to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: write
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.161.1
steps:
- name: Install Hugo CLI
run: |
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build with Hugo
run: |
hugo \
--minify \
--baseURL "${{ steps.pages.outputs.base_url }}/"
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: ./public
The workflow handles Hugo installation, theme submodules, minification, and deployment to GitHub Pages—all automatically.
Hexo vs Hugo: Key Differences
| Aspect | Hexo | Hugo |
|---|---|---|
| Language | Node.js (JavaScript) | Go |
| Build Speed | Slower for large sites | Extremely fast (sub-second builds) |
| Configuration | YAML (_config.yml) | TOML (hugo.toml) or YAML |
| Templating | EJS, Swig | Go templates |
| Plugin System | Node.js ecosystem | Go modules, shortcodes |
| Content Structure | source/_posts/ | content/posts/ |
| Learning Curve | Moderate | Moderate |
| Community Themes | Many | Growing rapidly |
Why I Chose Hugo
- Speed: Hugo’s build times are significantly faster, especially noticeable when working with many posts
- Single Binary: No Node.js dependency management headaches
- Built-in Features: Many features that require plugins in Hexo are built into Hugo
- Template Flexibility: Go templates offer powerful capabilities for custom layouts
Migration Process
The migration involved:
- Exporting existing Markdown posts to the new content directory
- Converting front matter from YAML to TOML format
- Configuring the new Hugo site with the Signal theme
- Setting up GitHub Actions for automatic deployment
- Testing locally and then pushing live
The entire process took approximately two days, with most time spent fine-tuning the theme design and ensuring all features worked correctly.
Acknowledgments
This migration wouldn’t have been possible without the incredible open-source communities behind both Hexo and Hugo:
- Hexo: Thank you for powering the first iteration of my blog and providing a solid foundation for static site generation
- Hugo: Thank you for the blazing-fast builds and flexible templating system that made this migration worthwhile
Both projects demonstrate the power of open-source software in enabling creators to share their knowledge and stories with the world.
Conclusion
Migrating from Hexo to Hugo was a rewarding experience that resulted in a faster, more customizable blogging platform. The Signal theme is now available for anyone who wants a clean, technical-focused Hugo theme. The GitHub Actions deployment pipeline ensures that new posts are automatically built and deployed with minimal friction.
If you’re considering a similar migration, I highly recommend Hugo for its speed and flexibility. And if you’re building something with the Signal theme, I’d love to hear about it!
Resources
Hugo Writing Workflow
Content Management
Hugo’s content structure is straightforward:
content/
├── posts/ # Blog posts
├── about/ # Static pages
└── archives/ # List pages
Use hugo new posts/my-article.md to create new posts with proper front matter. Hugo supports both TOML and YAML front matter formats, making migration from Hexo seamless.
Draft Management: Keep posts as drafts during writing by setting draft: true in front matter. Use hugo server -D to preview drafts locally.
Content Organization: Organize posts with tags and categories in front matter. Hugo’s section-based architecture makes it easy to group related content.
Daily Writing Workflow
- Create draft:
hugo new posts/article-title.md - Write locally: Use your preferred Markdown editor
- Preview:
hugo server -D(auto-reload on file changes) - Publish: Set
draft: falseand push to trigger GitHub Actions
Recommended Tools
VS Code - The most popular choice for Hugo development:
- Markdown All in One - Enhanced Markdown editing
- Hugo Shortcode Syntax Highlighting - Go template syntax support
- Front Matter - YAML/TOML front matter management
- Live Server - Preview with auto-reload
Obsidian - Excellent for knowledge management and linking:
- Works seamlessly with Hugo’s Markdown files
- Graph view helps visualize content relationships
- Templates can standardize front matter across posts
Terminal Tools:
hugo new- Create new contenthugo server -D- Local development server with draftshugo --minify- Production build
Front Matter Patterns
Basic post:
---
title: "My Article"
date: 2026-05-19T10:00:00+08:00
draft: true
tags: ['hugo', 'writing']
categories: ["Technical"]
summary: "Brief description for SEO"
---
Post with featured image:
---
title: "Tutorial Post"
date: 2026-05-19T10:00:00+08:00
draft: false
tags: ['tutorial']
featured_image: "/images/hero.png"
---
Setting Up Obsidian Templates
Obsidian templates streamline Hugo post creation with consistent front matter.
Step 1: Install Templater Plugin
- Open Obsidian Settings → Community plugins
- Install “Templater” plugin
- Enable the plugin
Step 2: Create Template Folder
Create templates/ folder in your Obsidian vault for storing templates.
Step 3: Create Hugo Post Template
Create templates/hugo-post.md with:
---
title: "<% tp.file.title %>"
date: <% tp.date.now("YYYY-MM-DDTHH:mm:ss+08:00") %>
draft: true
tags: []
categories: []
summary: ""
---
## Introduction
## Main Content
## Conclusion
Step 4: Configure Template Folder Location
- Settings → Templater
- Set “Template folder location” to
templates
Step 5: Use the Template
- Create new note in
content/posts/folder - Press
Ctrl/Cmd + P→ “Templater: Insert template” - Select
hugo-post.md - Template populates front matter with current date and title
Advanced Template with Prompts:
---
title: "<% tp.file.title %>"
date: <% tp.date.now("YYYY-MM-DDTHH:mm:ss+08:00") %>
draft: true
tags: ["<% tp.system.prompt("Enter tags (comma-separated)") %>"]
categories: ["<% tp.system.prompt("Enter category") %>"]
summary: "<% tp.system.prompt("Enter summary") %>"
---
Auto-Insert Templates on New File Creation
Method 1: Folder Templates (Built-in Obsidian)
- Settings → Files & Links → “Template file location”
- Settings → Files & Links → “Folder templates”
- Add folder rule:
- Folder:
content/posts/ - Template:
hugo-post.md
- Folder:
- New files in
content/posts/automatically get the template
Method 2: Templater Trigger on New File
Create templates/folder-trigger.md with folder template syntax:
<%*
let title = tp.file.title;
if (title.includes("Untitled")) {
title = await tp.system.prompt("Enter title:");
}
-%>
---
title: "<%* tR += title %>"
date: <% tp.date.now("YYYY-MM-DDTHH:mm:ss+08:00") %>
draft: true
tags: []
categories: []
summary: ""
---
## Introduction
## Main Content
## Conclusion
Then in Templater settings:
- Enable “Trigger Templater on new file creation”
- Set “User template folder” to
templates - Create a user function (Settings → Templater → User functions) to handle folder detection
Method 3: Hotkey Auto-Insert
- Settings → Hotkeys
- Search “Templater: Insert template”
- Assign shortcut (e.g.,
Ctrl/Cmd + Shift + N) - Type filename, then press shortcut to auto-insert template
Pro Tip: Combine with Obsidian’s “QuickAdd” plugin for even faster workflows:
- Install QuickAdd plugin
- Create macro: “New Hugo Post”
- Set command: Create file + insert template
- Assign macro to a hotkey for one-click post creation
Comments