Hexo Blog Structure

Hexo Logo

“Hexo is not just a static site generator; it’s a framework that empowers you to create, customize, and deploy beautiful blogs with minimal friction. Understanding its structure is the key to unlocking its full potential.”

📋 Table of Contents

  1. Introduction to Hexo
  2. The Root Directory Structure
  3. Deep Dive: Source Directory
  4. Understanding Themes
  5. Configuration Files Explained
  6. The Magic of Layouts
  7. Working with Partials
  8. Assets and Static Files
  9. Advanced Customization
  10. Deployment Strategies
  11. Best Practices and Tips
  12. Troubleshooting Common Issues

🌟 Introduction to Hexo

Hexo is a fast, simple, and powerful blog framework powered by Node.js. It transforms your plain text files (written in Markdown or other markup languages) into static HTML files that can be deployed anywhere. But what makes Hexo truly special is its elegant architecture and extensible design.

Why Understanding Structure Matters

When I first started with Hexo, I treated it like a black box—write posts, run hexo generate, and deploy. But as my blog grew, I needed to customize layouts, add new features, and optimize performance. That’s when I realized that understanding Hexo’s structure wasn’t just helpful—it was essential.

💡 Pro Tip: Think of Hexo as a well-organized house. Each directory has a specific purpose, and knowing where everything is located makes maintenance and customization much easier.


📁 The Root Directory Structure

Let’s start by examining the root directory of your Hexo blog. Here’s what you’ll typically see:

1
2
3
4
5
6
7
8
9
your-hexo-blog/
├── _config.yml # Main configuration file
├── package.json # Node.js dependencies
├── scaffolds/ # Post templates
├── source/ # Your content
├── themes/ # Theme files
├── public/ # Generated static files (auto-created)
├── .deploy_git/ # Deployment files (auto-created)
└── node_modules/ # Node.js packages (auto-created)

🔍 Key Files and Directories

_config.yml - The Heart of Your Blog

This is the main configuration file where you define:

  • Site metadata (title, description, author)
  • URL structure and permalinks
  • Directory settings
  • Writing preferences
  • Extension configurations
  • Deployment settings

Example Configuration:

1
2
3
4
5
6
7
8
9
10
# Site
title: My Awesome Blog
subtitle: 'A journey through code and creativity'
description: 'Welcome to my corner of the internet'
author: Your Name
language: entimezone: 'America/New_York'

# URL
url: https://yourblog.com
permalink: :year/:month/:day/:title/

package.json - Dependencies and Scripts

This file defines your project’s dependencies and includes useful scripts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "your-hexo-blog",
"version": "1.0.0",
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hexo server"
},
"dependencies": {
"hexo": "^6.3.0",
"hexo-renderer-marked": "^6.0.0",
"hexo-server": "^3.0.0"
}
}

📝 Deep Dive: Source Directory

The source/ directory is where your content lives. This is the most important directory for day-to-day blogging.

1
2
3
4
5
6
7
8
9
source/
├── _posts/ # Your blog posts
├── about/ # Custom pages
├── categories/ # Category pages
├── tags/ # Tag pages
├── images/ # Images and assets
├── css/ # Custom CSS files
├── js/ # Custom JavaScript files
└── index.md # Homepage content

📄 Blog Posts (_posts/)

This is where you’ll spend most of your time. Each post is a Markdown file with front matter:

1
2
3
4
5
6
7
8
9
---
title: "Understanding Hexo Structure"
date: 2025-08-07 15:30:00
tags: [hexo, blogging, web-development]
categories: [tutorials]
cover: /images/hexo-structure.jpg
---

# Your content goes here...

Front Matter Fields:

  • title: Post title
  • date: Publication date
  • tags: Array of tags
  • categories: Array of categories
  • cover: Featured image
  • layout: Custom layout (optional)
  • comments: Enable/disable comments (optional)

🗂️ Organizing Posts

You can organize posts in subdirectories:

1
2
3
4
5
6
7
8
9
_posts/
├── tutorials/
│ ├── hexo-basics.md
│ └── advanced-hexo.md
├── reviews/
│ ├── book-review.md
│ └── movie-review.md
└── personal/
└── my-journey.md

📑 Custom Pages

Create custom pages by adding directories and index.md files:

1
2
3
4
5
6
7
source/
├── about/
│ └── index.md
├── projects/
│ └── index.md
└── contact/
└── index.md

Example about/index.md:

1
2
3
4
5
6
7
8
9
---
title: About Me
layout: page
date: 2025-08-07 16:00:00
---

# About Me

Welcome to my personal space! I'm a passionate developer...

🎨 Understanding Themes

Themes control the visual appearance of your blog. Hexo comes with a default theme, but you can install and customize themes extensively.

1
2
3
4
5
6
7
themes/
└── your-theme/
├── _config.yml # Theme-specific configuration
├── languages/ # Translation files
├── layout/ # Template files
├── source/ # Theme assets
└── scripts/ # Theme scripts

🎭 Theme Structure Deep Dive

Layout Directory

1
2
3
4
5
6
7
8
themes/your-theme/layout/
├── _partial/ # Reusable template components
├── archive.ejs # Archive page template
├── category.ejs # Category page template
├── index.ejs # Homepage template
├── layout.ejs # Main layout template
├── page.ejs # Page template
└── post.ejs # Post template

Source Directory

1
2
3
4
5
6
themes/your-theme/source/
├── css/ # CSS files
├── fonts/ # Font files
├── images/ # Image assets
├── js/ # JavaScript files
└── lib/ # Third-party libraries

🛠️ Installing and Customizing Themes

  1. Install a theme:

    1
    npm install hexo-theme-cactus
  2. Enable it in _config.yml:

    1
    theme: cactus
  3. Customize theme settings:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # themes/cactus/_config.yml
    nav:
    home: /
    about: /about/
    archive: /archives/
    categories: /categories/
    tags: /tags/

    rss: true
    favicon: /favicon.ico

⚙️ Configuration Files Explained

Main Configuration (_config.yml)

Site Settings

1
2
3
4
5
6
7
# Site
title: toki pona today!
subtitle: 'tenpo suno la o toki!'
description: 'Welcome! **toki pona today** is a method to improve your comprehension...'
keywords:
author: jan Pitaki
language: entimezone: 'America/New_York'

URL Configuration

1
2
3
4
5
6
7
# URL
url: http://tokipona.today
permalink: :year/:month/:day/:title/
permalink_defaults:
pretty_urls:
trailing_index: true
trailing_html: true

Directory Settings

1
2
3
4
5
6
7
8
9
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:

Writing Settings

1
2
3
4
5
6
7
8
9
10
11
12
13
# Writing
new_post_name: :title.md
default_layout: post
titlecase: false
external_link:
enable: true
field: site
exclude: ''
filename_case: 0
render_drafts: false
post_asset_folder: true
relative_link: false
future: true

Home Page Settings

1
2
3
4
5
# Home page setting
index_generator:
path: 'Home'
per_page: 20
order_by: -date

Theme Configuration (themes/your-theme/_config.yml)

Theme configurations vary, but typically include:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Navigation
nav:
home: /
articles: /tags/lesson
practice: /tags/practice
blog: /tags/blog
other: /tags/other
links: http://tokipona.org
about: /About/

# Social links
social_links:
github: http://github.com/username
twitter: https://twitter.com/username
linkedin: https://linkedin.com/in/username
mail: mailto:email@example.com

# RSS feed
rss: true

# Analytics
google_analytics:
enabled: true
id: UA-XXXXXXXXX-X

🎭 The Magic of Layouts

Layouts are the templates that define how your content is displayed. Hexo uses EJS (Embedded JavaScript) templates by default.

Layout Hierarchy

1
2
3
4
5
6
layout.ejs (main layout)
├── _partial/header.ejs
├── _partial/footer.ejs
├── page.ejs (for pages)
├── post.ejs (for posts)
└── archive.ejs (for archives)

Main Layout (layout.ejs)

This is the master template that wraps all other templates:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<%- partial('_partial/head') %>
</head>
<body>
<%- partial('_partial/header') %>
<div class="content">
<%- body %>
</div>
<%- partial('_partial/footer') %>
</body>
</html>

Post Layout (post.ejs)

1
2
3
4
5
6
7
8
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<%- partial('_partial/post/gallery') %>
<div class="content" itemprop="articleBody">
<%- page.content %>
</div>
<%- partial('_partial/post/tag') %>
<%- partial('_partial/post/category') %>
</article>

Page Layout (page.ejs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<%- partial('_partial/post/gallery') %>
<div class="content" itemprop="articleBody">
<% if (page.search || page.type === "search") { %>
<%- partial('_partial/search') %>
<% } else if (page.type === "tags") { %>
<div id="tag-cloud">
<%- tagcloud({min_font: 12, max_font: 30, amount: 300}) %>
</div>
<% } else { %>
<%- page.content %>
<% } %>
</div>
</article>

Archive Layout (archive.ejs)

This is where we made our custom modification for the “other” tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="archive">
<% if (page.tag=="other") {%>
<div class="other-intro">
<% var otherPage = site.pages.findOne({path: 'Other/index.html'}); %>
<% if (otherPage) { %>
<%- otherPage.content %>
<% } %>
</div>
<% } %>

<ul class="post-list">
<% page.posts.each(function(post) { %>
<li class="post-item">
<%- partial('_partial/post/date', { post: post, class_name: 'meta' }) %>
<span><%- partial('_partial/post/title', { post: post, index: true, class_name: '' }) %></span>
</li>
<% }); %>
</ul>

<%- partial('_partial/pagination') %>
</div>

🔧 Working with Partials

Partials are reusable template components that help maintain consistency and reduce code duplication.

Common Partials

Header Partial (_partial/header.ejs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<header id="header">
<a href="/">
<div id="logo" style="background-image: url(<%= theme.logo.url %>);"></div>
<div id="title">
<h1><%= config.title %></h1>
</div>
</a>
<div id="nav">
<ul>
<% for (var key in theme.nav) { %>
<li><a href="<%- url_for(theme.nav[key]) %>"><%= key %></a></li>
<% } %>
</ul>
</div>
</header>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<footer id="footer">
<div class="footer-left">
Copyright &copy; <%= date(new Date(), 'YYYY') %> <%= config.author %>
</div>
<div class="footer-right">
<nav>
<ul>
<% for (var key in theme.nav) { %>
<li><a href="<%- url_for(theme.nav[key]) %>"><%= key %></a></li>
<% } %>
</ul>
</nav>
</div>
</footer>

Post Title Partial (_partial/post/title.ejs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<% if (post.link || post.title){ %>
<% if (post.link) { %>
<a href="<%- url_for(post.link) %>" target="_blank" itemprop="url">
<h1 itemprop="name">
<%= post.title %>
<span class="link-icon">→</span>
</h1>
</a>
<% } else if (post.title) { %>
<% if (index) { %>
<a class="<%= class_name %>" href="<%- url_for(post.path) %>">
<h1 itemprop="name"><%= post.title %></h1>
</a>
<% } else { %>
<h1 class="<%= class_name %>" itemprop="name"><%= post.title %></h1>
<% } %>
<% } %>
<% } %>

📁 Assets and Static Files

Post Assets

When post_asset_folder is enabled in _config.yml, Hexo creates a folder for each post’s assets:

1
2
3
4
5
6
_posts/
├── my-post/
│ ├── image1.jpg
│ ├── image2.png
│ └── diagram.svg
└── my-post.md

Referencing assets in your post:

1
2
{% asset_img image1.jpg "Image description" %}
{% asset_img image2.png "Another image" %}

Global Assets

For site-wide assets, use the source/ directory:

1
2
3
4
5
6
7
8
9
source/
├── images/
│ ├── logo.png
│ ├── favicon.ico
│ └── banner.jpg
├── css/
│ └── custom.css
└── js/
└── custom.js

Referencing global assets:

1
2
![Logo](/images/logo.png)
[Custom CSS](/css/custom.css)

Theme Assets

Theme-specific assets go in the theme’s source directory:

1
2
3
4
5
6
7
8
9
10
themes/your-theme/source/
├── css/
│ ├── style.css
│ └── highlight.css
├── js/
│ ├── main.js
│ └── plugins.js
└── images/
├── theme-logo.png
└── social-icons.svg

🚀 Advanced Customization

Custom Helpers

Create custom helpers in themes/your-theme/scripts/helpers.js:

1
2
3
4
5
6
7
hexo.extend.helper.register('reading_time', function(content) {
const wordsPerMinute = 200;
const text = content.replace(/<[^>]+>/g, '');
const words = text.split(/\s+/).length;
const minutes = Math.ceil(words / wordsPerMinute);
return `${minutes} min read`;
});

Usage in templates:

1
2
3
<div class="reading-time">
<%- reading_time(page.content) %>
</div>

Custom Generators

Create custom generators in themes/your-theme/scripts/generators.js:

1
2
3
4
5
6
7
hexo.extend.generator.register('custom_page', function(locals) {
return {
path: 'custom-page.html',
data: locals.theme.custom_data,
layout: 'custom_layout'
};
});

Custom Filters

Create custom filters in themes/your-theme/scripts/filters.js:

1
2
3
4
5
6
hexo.extend.filter.register('before_post_render', function(data) {
if (data.title) {
data.title = data.title.toUpperCase();
}
return data;
});

Custom Processors

Create custom processors for new file types:

1
2
3
4
5
6
hexo.extend.processor.register('txt', function(file) {
return {
path: file.path,
data: file.content.split('\n').map(line => `<p>${line}</p>`).join('\n')
};
});

🚢 Deployment Strategies

GitHub Pages

Configure in _config.yml:

1
2
3
4
deploy:
type: git
repo: git@github.com:username/username.github.io.git
branch: main

Deployment commands:

1
2
3
hexo clean
hexo generate
hexo deploy

Netlify

  1. Connect your GitHub repository to Netlify
  2. Set build command: hexo generate
  3. Set publish directory: public
  4. Add netlify.toml to root:
1
2
3
4
5
6
[build]
command = "hexo generate"
publish = "public"

[build.environment]
NODE_VERSION = "18"

Vercel

  1. Connect your GitHub repository to Vercel
  2. Set build command: hexo generate
  3. Set output directory: public
  4. Add vercel.json to root:
1
2
3
4
5
6
7
8
9
10
11
{
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "public"
}
}
]
}

Custom Server

For custom server deployment:

1
2
3
hexo generate
# Copy public/ directory to your web server
scp -r public/* user@server:/var/www/html/

💡 Best Practices and Tips

1. Version Control

Always keep your Hexo source under version control:

1
2
3
4
5
git init
git add .
git commit -m "Initial Hexo setup"
git remote add origin git@github.com:username/hexo-source.git
git push -u origin main

.gitignore file:

1
2
3
4
5
6
7
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/

2. Asset Organization

Organize your assets logically:

1
2
3
4
5
6
7
8
9
10
11
12
source/
├── images/
│ ├── posts/ # Post-specific images
│ ├── pages/ # Page-specific images
│ ├── icons/ # Icons and favicons
│ └── banners/ # Header banners
├── css/
│ ├── vendor/ # Third-party CSS
│ └── custom/ # Custom CSS
└── js/
├── vendor/ # Third-party JavaScript
└── custom/ # Custom JavaScript

3. Performance Optimization

  • Image optimization: Use WebP format and compress images
  • CSS/JS minification: Enable in _config.yml
  • Lazy loading: Implement for images
  • CDN: Use for static assets
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# _config.yml
minify:
html:
enable: true
exclude:
- '**/player.ejs'
css:
enable: true
exclude:
- '**/*.min.css'
js:
enable: true
exclude:
- '**/*.min.js'

4. SEO Best Practices

  • Meta tags: Configure in theme
  • Sitemap: Install hexo-generator-sitemap
  • Open Graph: Enable in theme config
  • Structured data: Add JSON-LD
1
npm install hexo-generator-sitemap --save

5. Security

  • Environment variables: Use for sensitive data
  • HTTPS: Enable on your domain
  • Content Security Policy: Implement headers
  • Regular updates: Keep Hexo and dependencies updated

6. Backup Strategy

  • Source backup: Git repository
  • Content backup: Regular exports
  • Database backup: If using external databases
  • Asset backup: Separate backup for media files

🔧 Troubleshooting Common Issues

1. Build Errors

Problem: hexo generate fails

1
2
ERROR Process failed: _posts/my-post.md
TypeError: Cannot read property 'length' of undefined

Solution: Check front matter syntax:

1
2
3
4
5
---
title: My Post
date: 2025-08-07 15:30:00
tags: [hexo, tutorial]
---

2. Theme Not Loading

Problem: Theme changes not reflected

1
2
INFO  Validating config
WARN No layout: index.html

Solution:

  • Check theme name in _config.yml
  • Ensure theme is installed: npm install hexo-theme-name
  • Clear cache: hexo clean

3. Asset Not Found

Problem: Images not displaying

1
GET /images/my-image.jpg 404 (Not Found)

Solution:

  • Check file paths
  • Use asset tags: {% asset_img my-image.jpg %}
  • Verify file exists in correct directory

4. Deployment Issues

Problem: GitHub Pages deployment fails

1
ERROR Deployer not found: git

Solution: Install deployer:

1
npm install hexo-deployer-git --save

5. Performance Issues

Problem: Site loads slowly

1
Page load time: 8.2s

Solution:

  • Optimize images
  • Minify CSS/JS
  • Enable caching
  • Use CDN

6. Plugin Conflicts

Problem: Plugins not working together

1
ERROR Plugin load failed: hexo-plugin-name

Solution:

  • Check plugin compatibility
  • Update plugins
  • Disable conflicting plugins temporarily

🎯 Conclusion: Mastering Your Hexo Blog

Understanding Hexo’s structure is like having a map to your blog’s architecture. It empowers you to:

  • Customize with confidence: Know exactly which files to modify
  • Troubleshoot effectively: Understand how components interact
  • Extend functionality: Add new features seamlessly
  • Maintain efficiently: Keep your blog organized and scalable

The Learning Journey

Remember, mastering Hexo is a journey. Start with the basics:

  1. Understand the directory structure
  2. Learn to modify templates
  3. Experiment with configurations
  4. Gradually tackle advanced customizations

Resources for Continued Learning

Final Thoughts

Hexo strikes the perfect balance between simplicity and power. Its modular architecture means you can start simple and gradually add complexity as needed. Whether you’re running a personal blog, a professional portfolio, or a complex content site, understanding Hexo’s structure gives you the foundation to build something truly remarkable.

“The beauty of Hexo lies not just in what it can do out of the box, but in what it enables you to create when you understand how it works.”


📚 Quick Reference Cheat Sheet

Essential Commands

1
2
3
4
5
6
hexo new "Post Title"           # Create new post
hexo new page "Page Name" # Create new page
hexo generate # Generate static files
hexo server # Start local server
hexo deploy # Deploy to production
hexo clean # Clean cache and generated files

Directory Structure

1
2
3
4
5
6
7
8
9
10
11
12
13
source/          # Your content
├── _posts/ # Blog posts
├── about/ # Custom pages
└── images/ # Images and assets

themes/ # Theme files
└── theme-name/
├── layout/ # Templates
├── source/ # Theme assets
└── _config.yml # Theme config

_config.yml # Main configuration
package.json # Dependencies and scripts

Common Template Variables

1
2
3
4
5
6
<%= config.title %>           # Site title
<%= page.title %> # Page title
<%= page.content %> # Page content
<%= page.date %> # Page date
<%= post.tags %> # Post tags
<%= post.categories %> # Post categories

Helper Functions

1
2
3
4
5
<%- url_for(path) %>         # Generate URL
<%- partial('partial-name') %> # Include partial
<%- date(date, format) %> # Format date
<%- tagcloud(options) %> # Generate tag cloud
<%- list_categories() %> # List categories

Happy blogging with Hexo! May your static sites be fast, your content be engaging, and your deployment process be smooth. 🚀