Hexo NexT Theme Supports Custom CDN Service Providers

abyss

The cause of the issue was that on December 20, 2021, JSDelivr had its ICP (Internet Content Provider) license revoked. As mainland China’s CDN services can only provide services for websites with a valid ICP license, the mainland CDN became ineffective. Fortunately, JSDelivr responded promptly by quickly switching to a global CDN after a brief fluctuation. Currently, access from mainland China is only slightly slower.

This blog uses the Hexo framework and NexT theme, with JSDelivr as the default CDN service provider for JS and CSS resources. Due to the aforementioned reasons, there’s a need to switch to a mainland CDN. NexT theme currently supports only local | jsdelivr | unpkg | cdnjs. Unfortunately, except for local, none of the others support mainland acceleration. Before NexT theme supports it in a new version, some minor modifications were made to support switching CDN service providers.

Hexo v8.9.0 now natively supports custom CDN provider configuration.

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}

Essentially, the solution involves modifying NexT’s CDN Settings configuration, expanding options to support personalized CDN service providers. However, due to the extensive resources involved, random replacement may lead to resource unavailability. Therefore, jsdelivr | unpkg | cdnjs mainland mirror sites were chosen for minimal changes and ensured compatibility.

The structure of the configuration remains unchanged; only the configuration items have become custom service providers, represented by a code, e.g., 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

Install the patch-package dependency:

npm install patch-package

Modify NexT’s source code to support personalized CDN service providers. This involves modifying two files:

Firstly, modify node_modules/hexo-theme-next/scripts/events/lib/vendors.js in one place. Copy and adjust the corresponding category according to which type of CDN mainland mirror site you are using. For example, if you are using the UNPKG mainland mirror site, the cdn-internal format would be https://xxxx//${name}@${version}/${file}.

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 in two places, similar to the modification above.

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 modification, execute npx patch-package hexo-theme-next to generate a patches file in the directory, recording the changes you made.

Finally, add a command in the package.json file so that each time dependencies are installed, the changes are automatically applied to the specified files. Hexo’s built-in deployment commands should still work properly.

{
  "scripts": {
    "build": "hexo generate",
    "clean": "hexo clean",
    "deploy": "hexo deploy",
    "server": "hexo server",
    "postinstall": "patch-package"
  }
}

For now, I’m using Zhimg’s CDN, which uses Alibaba Cloud’s global CDN, providing satisfactory results. However, as there is no explicit permission for external use from the official source, some risks exist, and misuse might result in permission revocation.

unpkg-zhimg-chengxiaobai-com-network

I’ve also examined the service capabilities of UNPKG, which are simpler than JSDelivr but still pose a risk of misuse. If there are further issues, self-hosting might be the only solution.

Reflecting on the JSDelivr incident, the underlying reasons are not deeply investigated. After all, as a service of charitable nature, it lacks the resources to scrutinize the content mentioned above. Moreover, its support for direct access to GitHub repositories is a fantastic feature. Free, convenient, and fast – these are the goals pursued by internet engineers, and to some extent, this is also its original sin, leading to various ingenious uses by “gurus,” resulting in the current situation we see.

Unfortunately today jsDelivr unexpectedly lost its ICP license in China. As a result, 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.

Observing the subsequent developments.

When you gaze long into the abyss, the abyss also gazes into you.