Hexo NexT Theme Supports Custom CDN Service Providers
The genesis of this matter can be traced back to December 20, 2021 when JSDelivr had its ICP license (record) revoked. Mainland China’s CDN services can only cater to websites with valid ICP licenses, thus rendering the mainland CDN service inoperative. However, JSDelivr’s official response was prompt, and after a brief hiccup, they swiftly migrated to a global CDN. Currently, the only noticeable impact for mainland China is a slight reduction in speed.
This blog utilizes the Hexo framework and NexT theme. By default, it relies on JSDelivr as the CDN service provider for JS and CSS resources. Due to the aforementioned reasons, there was a necessity to transition to a mainland CDN. As of now, NexT theme only supports ‘local | jsdelivr | unpkg | cdnjs’. Unfortunately, apart from ‘local’, the others are unable to support mainland acceleration. Therefore, prior to NexT’s support in a future update, some minor adjustments were made to facilitate the transition to CDN service providers.
Hexo v8.9.0 now natively supports the customization of CDN service providers.
vendors:
# The CDN provider of NexT internal scripts.
# Available values: local | jsdelivr | unpkg | cdnjs | custom
# Warning: If you are using the latest master branch of NexT, please set `internal: local`
internal: custom
# The default CDN provider of third-party plugins.
# Available values: local | jsdelivr | unpkg | cdnjs | custom
# Dependencies for `plugins: local`: https://github.com/next-theme/plugins
plugins: custom
# Custom CDN URL
# For example:
# custom_cdn_url: https://cdn.jsdelivr.net/npm/${npm_name}@${version}/${minified}
# custom_cdn_url: https://cdnjs.cloudflare.com/ajax/libs/${cdnjs_name}/${version}/${cdnjs_file}
custom_cdn_url: https://unpkg.zhimg.com/${npm_name}@${version}/${npm_file}
In essence, the solution involves modifying NexT’s CDN Settings configuration with an expansion option to support personalized CDN service providers. However, due to the large number of resources involved, haphazard replacement could result in resource unavailability. Therefore, for the time being, ‘jsdelivr | unpkg | cdnjs’ were selected as mainland mirror sites. This ensures the minimal alteration and compatibility.
The configuration structure remains unchanged; only the configuration items are designated as custom service providers, symbolized as ‘cdn-internal’:
vendors:
# The CDN provider of NexT internal scripts.
# Available values: local | jsdelivr | unpkg | cdnjs
# Warning: If you are using the latest master branch of NexT, please set `internal: local`
internal: cdn-internal
# The default CDN provider of third-party plugins.
# Available values: local | jsdelivr | unpkg | cdnjs
# Dependencies for `plugins: local`: https://github.com/next-theme/plugins
plugins: cdn-internal
To implement this, install the ‘patch-package’ dependency:
npm install patch-package
Make the necessary modifications to NexT’s source code to support personalized CDN service providers. Two files need to be altered. Firstly, modify ‘node_modules/hexo-theme-next/scripts/events/lib/vendors.js’ as follows:
const links = {
local : url_for.call(hexo, `lib/${name}/${file}`),
cdn-internal : `https://unpkg.zhimg.com/${name}@${version}/${file}`,
jsdelivr: `https://cdn.jsdelivr.net/npm/${name}@${version}/${file}`,
unpkg : `https://unpkg.com/${name}@${version}/${file}`,
cdnjs : `https://cdnjs.cloudflare.com/ajax/libs/${alias || name}/${version}/${file.replace(/^(dist|lib|)\/(browser\/|)/, '')}`
};
Secondly, modify ‘node_modules/hexo-theme-next/scripts/helpers/engine.js’ as follows:
hexo.extend.helper.register('next_js', function(file, pjax = false) {
const { next_version } = this;
const { internal } = this.theme.vendors;
const minified_file = file.endsWith('.js') && !file.endsWith('.min.js') ? file.slice(0, -3) + '.min.js' : file;
const links = {
local : this.url_for(`${this.theme.js}/${file}`),
cdn-internal : `https://unpkg.zhimg.com/hexo-theme-next@${next_version}/source/js/${file}`,
jsdelivr: `https://cdn.jsdelivr.net/npm/hexo-theme-next@${next_version}/source/js/${minified_file}`,
unpkg : `https://unpkg.com/hexo-theme-next@${next_version}/source/js/${file}`,
cdnjs : `https://cdnjs.cloudflare.com/ajax/libs/hexo-theme-next/${next_version}/${minified_file}`
};
const src = links[internal] || links.local;
return `<script ${pjax ? 'data-pjax ' : ''}src="${src}"></script>`;
});
hexo.extend.helper.register('next_pre', function() {
const { preconnect } = this.theme;
if (!preconnect) return '';
const { enable, host } = this.theme.font;
const { internal, plugins } = this.theme.vendors;
const links = {
local : '',
cdn-internal : 'https://unpkg.zhimg.com',
jsdelivr: 'https://cdn.jsdelivr.net',
unpkg : 'https://unpkg.com',
cdnjs : 'https://cdnjs.cloudflare.com'
};
const h = enable ? host || 'https://fonts.googleapis.com' : '';
const i = links[internal];
const p = links[plugins];
const results = [...new Set([h, i, p].filter(origin => origin))].map(
origin => `<link rel="preconnect" href="${origin}" crossorigin>`
);
return results.join('\n');
});
After making these modifications, execute npx patch-package hexo-theme-next
. This will generate a ‘patches’ file in your directory, which records the changes you made.
Finally, add a command in your ‘package.json’ file. This way, the changes will automatically apply to the specified files each time you install dependencies, and Hexo’s built-in deployment commands will continue to function normally.
{
"scripts": {
"build": "hexo generate",
"clean": "hexo clean",
"deploy": "hexo deploy",
"server": "hex
o server",
"postinstall": "patch-package"
}
}
Currently, the CDN in use is from Zhihu, which employs Alibaba Cloud’s global CDN. It has yielded satisfactory results. However, since the official stance on its public availability is not explicit, there exists a certain degree of risk. Misuse could result in immediate withdrawal of access rights.
Additionally, I have assessed UNPKG’s service capabilities. While simpler than JSDelivr, it too has the potential for misuse. In case of further contingencies, self-hosting might become necessary.
Reflecting on the JSDelivr incident, the underlying reasons need not be extensively explored. After all, it operates as a non-profit service, lacking the resources for content scrutiny. Moreover, it supports direct access to GitHub repositories, a feature of immense value. Free, convenient, and fast; these are the objectives sought after by internet engineers. Paradoxically, these very attributes may have contributed to its demise, as it was exploited in countless ways by various “gurus,” resulting in the current situation we observe.
Unfortunately today jsDelivr unexpectedly lost its ICP license in China. As effect the regional CDN disabled our account.
This resulted in the extended outage we had in mainland China and Taiwan.
Other regions were unaffected.
We understand how difficult it was for our users to experience this unique situation.
From now on all Chinese traffic will be served by “near China” locations provided by global CDN providers.
This will have the additional benefit of better failover logic in the future.
Awaiting further developments.
When you gaze long into an abyss, the abyss also gazes into you.