In Part 1, I covered why I switched from Jekyll to Hugo. Now let’s dive into the actual content migration.
Front Matter Conversion#
Most Jekyll posts work with minimal changes, but there are key differences:
# Jekyll
---
layout: post
title: "My Post"
date: 2024-01-15
categories: development
tags: [docker, containers]
mermaid: true
---
# Hugo
---
title: "My Post"
date: 2024-01-15
categories:
- Development
tags:
- Docker
- Containers
---Key changes I made:
- Removed
layout: post- Hugo infers layout from content location - Converted tags/categories to arrays - YAML list format
- Standardized capitalization - Consistent taxonomy naming
- Removed
mermaid: true- Blowfish auto-detects mermaid shortcodes
hugo server while migrating so you can preview each converted post immediately and catch front matter issues early.Shortcode Conversions#
Jekyll uses Liquid templates while Hugo has its own shortcode system.
Images and Figures#
<!-- Jekyll -->
{% include figure.html src="/images/photo.jpg" caption="My caption" %}
<!-- Hugo -->
{{< figure src="/images/photo.jpg" caption="My caption" >}}Here’s the Hugo figure shortcode in action:
Mermaid Diagrams#
This was a bigger change. Jekyll with the mermaid plugin uses fenced code blocks:
<!-- Jekyll -->
```mermaid
graph TD
A --> B
```Hugo with Blowfish requires the mermaid shortcode:
<!-- Hugo -->
{{< mermaid >}}
graph TD
A --> B
{{< /mermaid >}}I wrote a quick script to find and convert these across all posts. Here’s what a real mermaid diagram looks like after migration:
flowchart LR
A["Jekyll Post\n(.md + Liquid)"] -->|migrate| B["Hugo Post\n(.md + Shortcodes)"]
B --> C{"hugo build"}
C --> D["Static HTML"]
C --> E["Processed Images"]
C --> F["Minified CSS/JS"]
Code Blocks#
Standard fenced code blocks work the same, but Hugo adds features:
<!-- Hugo with line numbers -->
{{< highlight go "linenos=table,hl_lines=3" >}}
func main() {
fmt.Println("Hello")
fmt.Println("Highlighted!")
}
{{< /highlight >}}Here’s what that looks like rendered with Hugo’s syntax highlighting and line numbers:
| |
Static Assets#
Jekyll and Hugo organize assets differently:
| Jekyll | Hugo |
|---|---|
assets/images/ | static/images/ |
_data/ | data/ |
_includes/ | layouts/partials/ |
For images referenced in posts, I kept paths like /images/photo.jpg which maps to static/images/photo.jpg.
assets/ folder is for files processed by Hugo Pipes (SCSS, image resizing, fingerprinting). Use static/ for files served as-is. Mixing them up leads to 404s.Handling Excerpts#
Jekyll uses excerpt_separator in config or <!--more--> in posts. Hugo works the same way with <!--more-->:
---
title: "My Post"
---
This appears in the summary.
<!--more-->
This is the full content.Taxonomy Cleanup#
I took the opportunity to consolidate tags:
- Merged similar tags (
vscode→VSCode) - Standardized capitalization
- Removed unused categories
Bulk Migration Script#
“The best migration is the one you automate. Don’t hand-edit 60 posts when a script can do it in seconds.”
For 60+ posts, I used a simple PowerShell script:
Get-ChildItem "content/posts/*.md" | ForEach-Object {
$content = Get-Content $_.FullName -Raw
# Remove layout: post
$content = $content -replace "layout: post\r?\n", ""
# Remove mermaid: true
$content = $content -replace "mermaid: true\r?\n", ""
Set-Content $_.FullName $content
}{% raw %} blocks or custom Jekyll includes. Test thoroughly!What’s Next#
In Part 3, I’ll cover deployment with GitHub Actions and the challenges I encountered along the way.




