1 minute read

After running this blog on Hexo for two years, I migrated to Hugo. This post documents the migration process and the reasons behind the switch.

Why Leave Hexo?

Hexo served me well for writing security research posts, but several issues accumulated:

  1. Node.js dependency hell - node_modules grew to 200MB+ and npm audit showed dozens of vulnerabilities in dependencies I never directly used
  2. Build time - with 40+ posts, build time reached 15 seconds. Hugo builds the same content in under 200ms
  3. Markdown rendering differences - Hexo’s markdown renderer had subtle differences from GitHub-flavored markdown, causing code blocks to render incorrectly
  4. Plugin ecosystem - the Hexo plugin ecosystem became less active, with many plugins unmaintained

Why Hugo?

  • Single binary, no runtime dependencies
  • Extremely fast builds (measured at 180ms for my entire site)
  • Excellent code highlighting with Chroma
  • Built-in support for taxonomies (tags, categories)
  • Active development and growing ecosystem

Migration Process

Content

Hexo and Hugo both use YAML front matter and Markdown, so most posts required minimal changes:

  1. Copy all .md files from source/_posts/ to content/posts/
  2. Update front matter: Hexo uses tags: [a, b], Hugo uses the same format
  3. Fix image paths: Hexo uses its own asset_img tag, Hugo uses standard Markdown ![]()
  4. Update internal links between posts

URL Structure

The most critical part. My posts have external backlinks that must not break.

Hexo generated URLs like: /2019/12/08/HTTP-Smuggling-en/ Hugo can match this with the permalink configuration:

[permalinks]
  posts = '/:year/:month/:day/:title/'

I verified every existing URL returned 200 after migration.

Theme

I switched from the Hexo NexT theme to Hugo’s minimal-mistakes equivalent. The visual change is intentional - the old theme was getting dated, and I wanted a cleaner reading experience for technical content.

RSS Feed

Updated the feed URL from /atom.xml to Hugo’s default /index.xml. Added a redirect for the old URL.

Results

  • Build time: 15s to 180ms
  • Dependencies: 200MB node_modules to 0 (single Hugo binary)
  • Deployment: hugo && rsync takes under 2 seconds total

The migration took about 3 hours, most of which was spent verifying URLs and fixing code block rendering.