한국어 | English | 日本語
Senior Web Application Developer (8.8+ years)
Tech & Dev
engineering
Focusing on web frontend and backend development

Migrating to and Customizing New Versions of Hexo and Icarus

This post details the migration process to Hexo 5.0 and Icarus 4.0, undertaken to resolve a blog publishing error caused by a Node.js version conflict. Beyond a simple version update, it covers the practical customization record of analyzing the theme structure, which transitioned to JSX, and modifying the code at a component level to enhance blog readability.
This article shares solutions for library conflicts encountered during a major version update of the Hexo and Icarus themes. It details specific instances of implementing navigation bar logos, profile widgets, post width adjustments, and filtering logic for specific pages (About/Resume) to match the new JSX (Inferno.js) based theme structure, moving away from the previous EJS approach, complete with code examples.

Error Encountered During First Blog Post Publication in 2021

While I experience and learn many things through my work, I’ve found that if I don’t take the time to organize and document them somewhere, they simply fade from memory. I used to make good use of our internal wiki, but I wanted to share information and opinions with a broader audience, so I decided to publish a post on my blog again. However, when I tried to publish an article related to a recent issue using Hexo with hexo g -d, an error suddenly occurred.

TypeError [ERR_INVALID_ARG_TYPE]: The "mode" argument must be integer.
Received an instance of Object at copyFile

A quick Google search led me to a thread stating that the Node.js version needed to be downgraded, and the issue was fixed in Hexo 5.0.0 and later. It then occurred to me that I had updated all my library and framework versions at the end of last year for development studies. Among them, Node.js had been updated to version 15. It seems that updating Node.js globally to the latest version caused a conflict when Hexo attempted to run npm commands.

Hexo Latest Version Migration

I was using Hexo 3.8.0 / Icarus 2.3.0, but the latest versions I found were Hexo 5.3.0 / Icarus 4.1.1. To upgrade Hexo first, I cleared all existing packages in package.json and changed the Hexo version to 5.3.0. Rather than tediously adding each broken package after running npm install, I referred to the package.json automatically generated by running hexo init in a new directory.

Hexo's package.json Version Change Logs

Conflict Between Latest Hexo and Old Icarus

After updating Hexo and running hexo server to preview the page locally, errors continuously occurred in the Icarus theme files, stating that specific variables could not be found. A Google search revealed this was also a version issue. So, I backed up only the custom _config.yml settings of the existing Icarus theme, deleted everything else, and then installed Icarus in its latest version (1) not via npm install but (2) via git submodule (for customization purposes). When I ran it again, I encountered a problem where only JSX code was displayed. Initially thinking it was a React.js issue due to JSX, I realized it was developed with Inferno.js and resolved it by installing that library.

Icarus JSX Customization

Due to my meticulous nature, which compels me to modify any theme, I had previously customized the old Icarus theme by analyzing its EJS code. The old EJS-based Icarus had now become JSX-based, meaning my previous customization code couldn’t be used as-is, and I had to re-analyze and modify it. Having done frontend development primarily with React.js, and since Inferno.js (the framework Icarus is built on) is React-like, I anticipated that it wouldn’t be too difficult if I could debug it, and indeed, it wasn’t. However, as the Icarus project transitioned to JSX, its previously messy EJS structure became well-modularized into components. This meant that parts requiring different rendering per page had to be handled by adding exception conditions to common components. You’ll understand better by looking at the customized code below.

Hexo Debugging with VSCode

To modify a theme, the first step is to understand how Hexo and Icarus JSX components are rendered into a page. The hexo server command, which you run locally to test Hexo, executes an npm script defined in hexo-cli. Any changes to theme settings or posts are immediately applied to the local page because npm dynamically renders them. I used VSCode to debug to understand how the Icarus theme operates and to verify that my modified code worked correctly. Instructions for debugging with VSCode are well-explained in my other blog post, “How to Debug Hexo in VSCode,” which you can refer to.

While the navigation bar at the top is typically configured to display a logo image, I replaced it with a word that represents this blog and adjusted its font size.

Named Logo Navigation Bar

_config.icarus.yml
# Path or URL to the website's logo
logo:
    text: Crucian Carp
include/style/navbar.styl
.navbar-logo
    img
        max-height: $logo-height
    font-size: 1.4rem

Left Widget - Profile Reset

For this blog, I wanted the post structure to be as simple as possible, even at the cost of some web search benefits, so I don’t use any tags. In the left profile widget, I removed the code for displaying the tag count, showing only the number of posts and categories.

Tag Removed Profile

layout/widget/profile.jsx
<div class="level-item has-text-centered is-marginless">
    <div>
        <p class="heading">{counter.tag.title}</p>
        <a href={counter.tag.url}>
            <p class="title">{counter.tag.count}</p>
        </a>
    </div>
</div>
layout/widget/profile.jsx
tag: {
    count: tagCount,
    title: _p('common.tag', tagCount),
    url: url_for('/tags')
}

Post Header Time Format Change

The Icarus theme typically displays how long it has been since a post was first written and since it was last updated at the top of the post. Personally, I prefer to see the original creation date of the post in a date format, similar to old WYSIWYG blogs like Naver, so I changed this as well.

Erased Elapsed Time

layout/common/article.jsx
<div class="level-left">
    {/* Creation Date */}
    {page.date && <span class="level-item" dangerouslySetInnerHTML={{
        __html: _p(`<div>${date(page.date)}</div>`)
    }}></span>}
    {/* author */}
    {page.author ? <span class="level-item"> {page.author} </span> : null}

Font Change

When I first started my Hexo blog, I decided to write all posts in Korean. I found that a slightly wider letter spacing improves readability, so I chose to continue using the Nanum Gothic font, which I’ve used since the beginning.

include/style/base.styl
$family-sans-serif ?= 'Ubuntu', 'Nanum Gothic', sans-serif
$family-code ?= 'Source Code Pro', monospace, 'Microsoft YaHei'

Widget & Post Width Reset

Icarus offers two widgets: (1) right and (2) left. I concluded that using both could reduce the post width in the middle, impairing readability. Even when using only the left widget, I felt its width was too long compared to the post width, so I adjusted it.

Previous Widget Width

Newest Widget Width

Icarus’s width distribution uses Bulma CSS’s 12-column grid rule. Previously, it was as follows:

Since this blog will only use the left widget for post readability, I redefined the width distribution to:

The post width was increased from 8 to 9, and the widget width was reduced from 4 to 3.

layout/common/widgets.jsx
function getColumnSizeClass(columnCount) {
    switch (columnCount) {
        case 2:
            return 'is-3-tablet is-3-desktop is-3-widescreen';
        case 3:
            return 'is-3-tablet is-3-desktop is-2-widescreen';
    }
    return '';
}
layout/layout.js
<div class="columns">
    <div class={classname({
        column: true,
        'order-2': true,
        'column-main': true,
        'is-12': columnCount === 1,
        'is-9-tablet is-9-desktop is-9-widescreen': columnCount === 2,
        'is-9-tablet is-9-desktop is-8-widescreen': columnCount === 3
    })} dangerouslySetInnerHTML={{ __html: body }}></div>
    <Widgets site={site} config={config} helper={helper} page={page} position={'left'} />
    <Widgets site={site} config={config} helper={helper} page={page} position={'right'} />
</div>

About Page Widget and Plugin Removal

By clicking “About” in the top navigation bar, you can find general information about me. I also created a separate personal Resume page, allowing visitors to view my career history at a glance without having to access Google Docs or LinkedIn.

In the EJS era of the old Icarus, both the About and Resume pages had their own separate EJS files, so I only needed to modify those specific pages. However, in the new JSX-based Icarus, the post component was used as the base component for all pages, including About and Resume. To address this, I implemented logic to filter and prevent widgets and plugins from displaying on specific pages by creating a static list of those pages.

Previous About Page

Newest About Page

layout/layout.jsx
const isAboutPage = [ "about/index.html", "resume/index.html" ].includes(page.path);
layout/layout.jsx
<Head site={site} config={config} helper={helper} page={page} />
<body class={`is-${columnCount}-column`}>
    <Navbar config={config} helper={helper} page={page} />
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class={classname({
                    column: true,
                    'order-2': true,
                    'column-main': true,
                    'is-12': columnCount === 1,
                    'is-9-tablet is-9-desktop is-9-widescreen': columnCount === 2,
                    'is-9-tablet is-9-desktop is-8-widescreen': columnCount === 3
                })} dangerouslySetInnerHTML={{ __html: body }}></div>
                {!isAboutPage && <Widgets site={site} config={config} helper={helper} page={page} position={'left'} />}
                {!isAboutPage && <Widgets site={site} config={config} helper={helper} page={page} position={'right'} />}
layout/common/article.jsx
const isAboutPage = [ "about/index.html", "resume/index.html" ].includes(page.path);
layout/common/article.jsx
{/* Licensing block */}
{!isAboutPage && !index && article && article.licenses && Object.keys(article.licenses)
    ? <ArticleLicensing.Cacheable page={page} config={config} helper={helper} /> : null}

{/* Tags */}
{!isAboutPage && !index && page.tags && page.tags.length ? <div class="article-tags is-size-7 mb-4">
    <span class="mr-2">#</span>
    {page.tags.map(tag => {
        return <a class="link-muted mr-2" rel="tag" href={url_for(tag.path)}>{tag.name}</a>;
    })}
</div> : null}

{/* "Read more" button */}
{!isAboutPage && index && page.excerpt ? <a class="article-more button is-small is-size-7" href={`${url_for(page.link || page.path)}#more`}>{__('article.more')}</a> : null}

{/* Share button */}
{!isAboutPage && !index ? <Share config={config} page={page} helper={helper} /> : null}

{/* Donate button */}
{!isAboutPage && !index ? <Donates config={config} helper={helper} /> : null}

{/* Post navigation */}
{!isAboutPage && !index && (page.prev || page.next) ? <nav class="post-navigation mt-4 level is-mobile">

{/* Comment */}
{!isAboutPage && !index ? <Comment config={config} page={page} helper={helper} /> : null}

The blog and this post (from a previous version created in 2021, before the current iteration of this blog) were configured with the Icarus theme, fully customized as described above. I hope this article is helpful to those who adopted Hexo or Icarus several years ago and are now considering migration or wishing to perform custom modifications. While there might be minor CSS and JSX adjustments in the future, I do not intend to update them all separately.

Migrating to and Customizing New Versions of Hexo and Icarus
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
More in this category
Recent posts
The Erosion of Conversational Muscle and Communication Styles by LLM Filters
In an era where LLM tools, which filter out conversational impoliteness and deliver refined responses, have become commonplace, are we truly engaging in more thoughtful conversations? This article examines the phenomenon of conversational ability, which should be honed through countless failures in real-time communication, degenerating due to reliance on external tools. It further explores the potential societal anxieties and shifts in generational behavioral patterns that this trend may bring.
Optimal Timing and Strategy for Salary Negotiation with Senior Candidates
Salary negotiation is more than just an exchange of figures; it's a strategic dance of psychological timing. This analysis explores why engaging in a gradual negotiation process from the initial stages of recruitment, rather than waiting until after a final offer (when candidates tend to adopt a more calculative stance), proves more efficient for companies and fosters a more honest sharing of resources.
The Limits of the Rule of Law and Human Diversity
The belief that all human actions can be regulated by a single legal system may be an act of hubris. This article offers a sharp analysis of the paradox of the rule of law faced by humanity, which, having escaped the hierarchical controls of the Middle Ages, has now embraced infinite modern freedom. It further examines the deepening social coercion and the demonization of others that arise under the guise of diversity.
토스트 예시 메세지