Migrating to and Customizing New Versions of Hexo and Icarus
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.

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.
Navigation Bar Logo
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.

# Path or URL to the website's logo
logo:
text: Crucian Carp
.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.

<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>
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.

<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.
$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.
- 4 (Left Widget) + 8 (Content) = 12

- 3 (Left Widget) + 9 (Content) = 12

Icarus’s width distribution uses Bulma CSS’s 12-column grid rule. Previously, it was as follows:
- 4 (Left Widget) + 8 (Content) = 12
- 8 (Content) + 4 (Right Widget) = 12
- 3 (Left Widget) + 6 (Content) + 3 (Right Widget) = 12
Since this blog will only use the left widget for post readability, I redefined the width distribution to:
- 3 (Left Widget) + 9 (Content) = 12
- 9 (Content) + 3 (Right Widget) = 12
The post width was increased from 8 to 9, and the widget width was reduced from 4 to 3.
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 '';
}
<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.
- Widgets are one thing, but buy me a coffee is the key point to remove.

- The About page should only be about me.

const isAboutPage = [ "about/index.html", "resume/index.html" ].includes(page.path);
<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'} />}
const isAboutPage = [ "about/index.html", "resume/index.html" ].includes(page.path);
{/* 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.