都2025年了,还在羡慕他人在自己的文章下有“文章推荐”模块吗?使用hexo-article-recommender可以一步满足!它会在文章底部的文章推荐模块,会根据文章的关键字自动进行推荐,非常适合希望提升读者停留时间和内容发现效率的 Hexo 博主。

hexo-article-recommender是一款开箱即用、纯本地运行的Hexo智能文章推荐插件,融合语义、标题、标签、分类和时效性五大维度,无需网络请求,兼顾隐私、速度与精准推荐。

不发起任何网络请求,保障隐私、提升加载速度;所有推荐逻辑在本地 Node.js 环境中完成;仅在 hexo generate 时进行一次本地计算,运行时无开销。

github地址:https://github.com/i-NMB/hexo-article-recommender

一、效果图

话不多说,先上效果图,这是一个在文章的末尾添加相关文章的插件,它通过分析文章的关键词、标签或分类等元数据,生成相关性推荐列表,帮助读者发现更多相关内容,提升博客的浏览体验和用户粘性。

示例:一些笔记 - i囡漫笔

二、安装

由于hexo-article-recommender内置了模板,仅需在博客的根目录使用npm安装插件即可使用默认模板。开箱即用,安装后无需任何配置即可自动在文章底部显示推荐内容。

npm install hexo-article-recommender --save

三、配置(可选)

以下配置均可通过自己的需求进行!

3.1. _config.yml配置

你可以通过在博客根目录的_config.yml中选择添加以下配置项来设置自己喜欢的选项。

article_recommender:
  # 是否自动将推荐模块注入到文章页面底部(默认 true)
  inject: true
  # 推荐策略(可选:balanced | title-focused | content-focused,默认 balanced)
  strategy: balanced
  # 默认推荐文章数量(默认 3)
  default_count: 3
  # 推荐区域标题(支持中英文)
  recommended_title: "推荐文章"
  # 时效性衰减系数(值越大,旧文章权重下降越快;默认 0.3)
  recency_decay: 0.3

推荐策略(strategy)的占比如下:

策略名称语义占比标题占比标签占比分类占比时效占比

balanced(默认)

50%

20%

15%

10%

5%

title-focused

30%

40%

15%

10%

5%

content-focused

70%

10%

10%

5%

5%

如果你还有什么好的占比建议可以直接在评论区或者在github的开源项目上留言

3.2. 手动注入(高级)

自动注入(默认):开启 inject: true 即可。

该插件通常通过 Hexo 的 injects 机制,将推荐内容注入到主题模板中的 </article> 标签之后。但是如果遇到</article> 标签之后不适用,或者在该位置的背景颜色与插件默认的颜色不搭,可以尝试手动注入模板。

步骤一:关闭自动注入

在博客的根目录的_config.yml中关闭并配置自动注入选项:

article_recommender:
  inject: false
步骤二:创建并引入独立的推荐模板文件(示例)
EJS 模板(如 landscape、fluid 等主题)

 详见Hexo博客EJS主题的相关文章智能推荐插件hexo-article-recommender进阶配置(DIY)https://i-nmb.cn/hexo-article-recommender-ejs.html

在你的主题目录下(如 /themes/your-theme/layout/_partial/)新建文件:

themes/your-theme/layout/_partial/article-recommender.ejs

内容为

<% if (posts.length) { %>
<div class="hexo-article-recommender-section">
  <div class="hexo-article-recommender-section-divider">
    <div class="hexo-article-recommender-divider-line"></div>
    <div class="hexo-article-recommender-divider-text">
      <span><%= recommendedTitle %></span>
    </div>
  </div>

  <div class="hexo-article-recommender-posts">
    <% posts.forEach((link, idx) => { %>
      <% const bgColors = ['#6a89cc, #4a69bd', '#e55039, #eb2f06', '#3dc1d3, #0fb9b1', '#f6b93b, #e55039'] %>
      <% const avatarColors = ['#4a69bd', '#e55039', '#3dc1d3', '#f6b93b'] %>
      <div class="hexo-article-recommender-post-card" data-bg="<%= bgColors[idx % bgColors.length] %>">
        <!-- 左上角:站内标签 -->
        <div class="hexo-article-recommender-site-tag">站内</div>

        <!-- 左下角:关系类型标签 + 相关度 -->
        <div class="hexo-article-recommender-relation-tag <%= link._recommendType === 'related' ? 'related' : 'fallback' %>">
          <%= link._recommendType === 'related' ? '相关' : '推荐' %>
          <span class="hexo-article-recommender-score">(<%= (link._recommendScore).toFixed(1) %>%)
        </span>
        </div>

        <div class="hexo-article-recommender-card-header">
          <div class="hexo-article-recommender-reading-time">
            <i class="fas fa-clock"></i>
            <%= link.minutes || link.reading_time || (link.symbols_count_time && link.symbols_count_time.mins) || '阅读' %>分钟
          </div>
        </div>
        <div class="hexo-article-recommender-card-content">
          <h4 class="hexo-article-recommender-post-title">
            <a href="<%- url_for(link.path) %>"><%= link.title %></a>
          </h4>
          <p class="hexo-article-recommender-post-excerpt">
            <%- (link.description || (link.excerpt && strip_html(link.excerpt)) || (link.content && strip_html(link.content)) || '点击查看推荐内容。').substring(0, 80) %>
          </p>
          <div class="hexo-article-recommender-author">
            <div class="hexo-article-recommender-author-avatar" style="background: <%= avatarColors[idx % avatarColors.length] %>;">
              <%= idx + 1 %>
            </div>
            <div class="hexo-article-recommender-author-info">
              <div class="hexo-article-recommender-author-name"><%= link.author || link.author_name || link.author_id || '本站作者' %></div>
              <div class="hexo-article-recommender-author-title"><%= link.author_title || '原创文章' %></div>
            </div>
          </div>
        </div>
      </div>
    <% }) %>
  </div>
  <h2 class="hexo-article-recommender-section-over">--- over ---</h2>
</div>

<style>
.hexo-article-recommender-section {
  margin-top: 60px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.hexo-article-recommender-section-divider {
  position: relative;
  margin: 60px 0;
}

.hexo-article-recommender-divider-line {
  height: 1px;
  background: linear-gradient(90deg, transparent, #6a89cc, transparent);
}

.hexo-article-recommender-divider-text {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  background: white;
  padding: 0 15px;
}

.hexo-article-recommender-divider-text span {
  color: #6a89cc;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 1px;
}

.hexo-article-recommender-section-over {
  text-align: center;
  margin: 30px;
  color: #9e9e9e;
}

.hexo-article-recommender-posts {
  display: flex;
  flex-wrap: wrap;
  gap: 25px;
  justify-content: center;
}

.hexo-article-recommender-post-card {
  width: 280px;
  background: #f8f9fa;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  transition: transform 0.35s ease, box-shadow 0.35s ease;
  display: flex;
  flex-direction: column;
  min-height: 270px;
  position: relative;
}

.hexo-article-recommender-post-card:hover {
  transform: translateY(-8px);
  box-shadow: 0 10px 25px rgba(0,0,0,0.15);
}

@keyframes gradientShift {
  0% { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

.hexo-article-recommender-card-header {
  height: 60px;
  position: relative;
  background: linear-gradient(135deg, var(--bg-start), var(--bg-end));
  background-size: 200% 200%;
  animation: gradientShift 4s ease infinite alternate;
}

.hexo-article-recommender-post-card[data-bg^="#6a89cc"] .hexo-article-recommender-card-header {
  --bg-start: #6a89cc; --bg-end: #4a69bd;
}
.hexo-article-recommender-post-card[data-bg^="#e55039"] .hexo-article-recommender-card-header {
  --bg-start: #e55039; --bg-end: #eb2f06;
}
.hexo-article-recommender-post-card[data-bg^="#3dc1d3"] .hexo-article-recommender-card-header {
  --bg-start: #3dc1d3; --bg-end: #0fb9b1;
}
.hexo-article-recommender-post-card[data-bg^="#f6b93b"] .hexo-article-recommender-card-header {
  --bg-start: #f6b93b; --bg-end: #e55039;
}

.hexo-article-recommender-reading-time {
  position: absolute;
  top: 12px;
  right: 12px;
  background: rgba(255,255,255,0.25);
  backdrop-filter: blur(4px);
  color: white;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
}

.hexo-article-recommender-card-content {
  padding: 20px;
  display: flex;
  flex-direction: column;
  height: calc(100% - 60px);
  justify-content: space-between;
}

.hexo-article-recommender-post-title {
  margin-top: 0;
  font-size: 1.2em;
  line-height: 1.4;
  color: #2c3e50;
}

.hexo-article-recommender-post-title a {
  color: inherit;
  text-decoration: none;
  transition: color 0.25s ease;
}

.hexo-article-recommender-post-title a:hover {
  color: #6a89cc;
}

.hexo-article-recommender-post-excerpt {
  color: #666;
  font-size: 14px;
  line-height: 1.6;
  margin: 10px 0;
  overflow: hidden;
}

.hexo-article-recommender-author {
  display: flex;
  align-items: center;
}

.hexo-article-recommender-author-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  margin-right: 10px;
}

.hexo-article-recommender-author-name {
  font-weight: 600;
  font-size: 14px;
}

.hexo-article-recommender-author-title {
  font-size: 12px;
  color: #888;
}

/* ========== 左上角:站内标签 ========== */
.hexo-article-recommender-site-tag {
  position: absolute;
  top: 0;
  left: 0;
  background-color: rgba(42, 170, 138, 0.9);
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 4px 8px;
  border-radius: 0 0 8px 0;
  z-index: 2;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 1;
  transform: translateX(0);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* ========== 左下角:关系类型标签 ========== */
.hexo-article-recommender-relation-tag {
  position: absolute;
  bottom: 0;
  left: 0;
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 4px 8px;
  border-radius: 0 8px 0 0;
  z-index: 2;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.hexo-article-recommender-relation-tag.related {
  background-color: rgba(42, 170, 138, 0.9); /* 绿色:相关 */
}

.hexo-article-recommender-relation-tag.fallback {
  background-color: rgba(229, 80, 57, 0.9); /* 橙红:推荐 */
}

.hexo-article-recommender-post-card:hover .hexo-article-recommender-relation-tag {
  opacity: 1;
  transform: translateY(0);
}

/* 相关度分数样式 */
.hexo-article-recommender-relation-tag .hexo-article-recommender-score {
  font-size: 10px;
  opacity: 0.8;
  margin-left: 4px;
}

/* 响应式 */
@media (max-width: 768px) {
  .hexo-article-recommender-posts {
    flex-direction: column;
    align-items: center;
  }
  .hexo-article-recommender-post-card {
    width: calc(100% - 30px);
    max-width: 400px;
  }
}
</style>
<% } %>

之后在对应的post模板中(\themes\you-theme-name\layout\post.ejs)找到想注入的地方(自己喜欢在哪就添加在哪)添加,其中include内容要指向上一步添加的recommended-posts.ejs文件路径

<% 
  const cfg = config.article_recommender || {};
  const _ar_data = { 
    posts: recommended_posts_full(page, (cfg.default_count) || 3),
    recommendedTitle: cfg.recommended_title || '推荐文章主题推荐'
  };
%>
<%- partial('_partial/article-recommender', _ar_data) %>
Nunjucks模板(如NexT主题等)

详见Hexo博客NJK主题的相关文章智能推荐插件hexo-article-recommender进阶配置(DIY)https://i-nmb.cn/hexo-article-recommender-njk.html

以NexT为例,在\themes\next\layout\_partials中新建文件

\themes\next\layout\_partials\recommended-posts.njk

内容为:

{%- if posts and posts.length > 0 -%}
<div class="hexo-article-recommender-section">
  <div class="hexo-article-recommender-section-divider">
    <div class="hexo-article-recommender-divider-line"></div>
    <div class="hexo-article-recommender-divider-text">
      <span>{{ recommendedTitle or '推荐文章' }}</span>
    </div>
  </div>

  <div class="hexo-article-recommender-posts">
    {%- for link in posts -%}
      {%- set bgColors = [
        '#6a89cc, #4a69bd',
        '#e55039, #eb2f06',
        '#3dc1d3, #0fb9b1',
        '#f6b93b, #e55039'
      ] -%}
      {%- set avatarColors = ['#4a69bd', '#e55039', '#3dc1d3', '#f6b93b'] -%}
      {%- set idx = loop.index0 -%}
      <div class="hexo-article-recommender-post-card" data-bg="{{ bgColors[idx % bgColors.length] }}">
        <div class="hexo-article-recommender-site-tag">站内</div>

        <div class="hexo-article-recommender-relation-tag {{ 'related' if link._recommendType == 'related' else 'fallback' }}">
          {{ '相关' if link._recommendType == 'related' else '推荐' }}
          <span class="hexo-article-recommender-score">({{ link._recommendScore | round(1) }}%)</span>
        </div>

        <div class="hexo-article-recommender-card-header">
          <div class="hexo-article-recommender-reading-time">
            <i class="fas fa-clock"></i>
            {{ link.minutes or link.reading_time or (link.symbols_count_time and link.symbols_count_time.mins) or '阅读' }}分钟
          </div>
        </div>
        <div class="hexo-article-recommender-card-content">
          <h4 class="hexo-article-recommender-post-title">
            <a href="{{ url_for(link.path) }}">{{ link.title }}</a>
          </h4>
          <p class="hexo-article-recommender-post-excerpt">
            {{ (link.description or (link.excerpt and strip_html(link.excerpt)) or (link.content and strip_html(link.content)) or '点击查看推荐内容。') | truncate(80, false) }}
          </p>
          <div class="hexo-article-recommender-author">
            <div class="hexo-article-recommender-author-avatar" style="background: {{ avatarColors[idx % avatarColors.length] }};">
              {{ idx + 1 }}
            </div>
            <div class="hexo-article-recommender-author-info">
              <div class="hexo-article-recommender-author-name">{{ link.author or link.author_name or link.author_id or '本站作者' }}</div>
              <div class="hexo-article-recommender-author-title">{{ link.author_title or '原创文章' }}</div>
            </div>
          </div>
        </div>
      </div>
    {%- endfor -%}
  </div>
  <h2 class="hexo-article-recommender-section-over">--- over ---</h2>
</div>

<style>
.hexo-article-recommender-section {
  margin-top: 60px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.hexo-article-recommender-section-divider {
  position: relative;
  margin: 60px 0;
}

.hexo-article-recommender-divider-line {
  height: 1px;
  background: linear-gradient(90deg, transparent, #6a89cc, transparent);
}

.hexo-article-recommender-divider-text {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  background: white;
  padding: 0 15px;
}

.hexo-article-recommender-divider-text span {
  color: #6a89cc;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 1px;
}

.hexo-article-recommender-section-over {
  text-align: center;
  margin: 30px;
  color: #9e9e9e;
}

.hexo-article-recommender-posts {
  display: flex;
  flex-wrap: wrap;
  gap: 25px;
  justify-content: center;
}

.hexo-article-recommender-post-card {
  width: 280px;
  background: #f8f9fa;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  transition: transform 0.35s ease, box-shadow 0.35s ease;
  display: flex;
  flex-direction: column;
  min-height: 270px;
  position: relative;
}

.hexo-article-recommender-post-card:hover {
  transform: translateY(-8px);
  box-shadow: 0 10px 25px rgba(0,0,0,0.15);
}

@keyframes gradientShift {
  0% { background-position: 0% 50%; }
  100% { background-position: 100% 50%; }
}

.hexo-article-recommender-card-header {
  height: 60px;
  position: relative;
  background: linear-gradient(135deg, var(--bg-start), var(--bg-end));
  background-size: 200% 200%;
  animation: gradientShift 4s ease infinite alternate;
}

.hexo-article-recommender-post-card[data-bg^="#6a89cc"] .hexo-article-recommender-card-header {
  --bg-start: #6a89cc; --bg-end: #4a69bd;
}
.hexo-article-recommender-post-card[data-bg^="#e55039"] .hexo-article-recommender-card-header {
  --bg-start: #e55039; --bg-end: #eb2f06;
}
.hexo-article-recommender-post-card[data-bg^="#3dc1d3"] .hexo-article-recommender-card-header {
  --bg-start: #3dc1d3; --bg-end: #0fb9b1;
}
.hexo-article-recommender-post-card[data-bg^="#f6b93b"] .hexo-article-recommender-card-header {
  --bg-start: #f6b93b; --bg-end: #e55039;
}

.hexo-article-recommender-reading-time {
  position: absolute;
  top: 12px;
  right: 12px;
  background: rgba(255,255,255,0.25);
  backdrop-filter: blur(4px);
  color: white;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
}

.hexo-article-recommender-card-content {
  padding: 20px;
  display: flex;
  flex-direction: column;
  height: calc(100% - 60px);
  justify-content: space-between;
}

.hexo-article-recommender-post-title {
  margin-top: 0;
  font-size: 1.2em;
  line-height: 1.4;
  color: #2c3e50;
}

.hexo-article-recommender-post-title a {
  color: inherit;
  text-decoration: none;
  transition: color 0.25s ease;
}

.hexo-article-recommender-post-title a:hover {
  color: #6a89cc;
}

.hexo-article-recommender-post-excerpt {
  color: #666;
  font-size: 14px;
  line-height: 1.6;
  margin: 10px 0;
  overflow: hidden;
}

.hexo-article-recommender-author {
  display: flex;
  align-items: center;
}

.hexo-article-recommender-author-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  margin-right: 10px;
}

.hexo-article-recommender-author-name {
  font-weight: 600;
  font-size: 14px;
}

.hexo-article-recommender-author-title {
  font-size: 12px;
  color: #888;
}

/* ========== 左上角:站内标签 ========== */
.hexo-article-recommender-site-tag {
  position: absolute;
  top: 0;
  left: 0;
  background-color: rgba(42, 170, 138, 0.9);
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 4px 8px;
  border-radius: 0 0 8px 0;
  z-index: 2;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 1;
  transform: translateX(0);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

/* ========== 左下角:关系类型标签 ========== */
.hexo-article-recommender-relation-tag {
  position: absolute;
  bottom: 0;
  left: 0;
  color: white;
  font-size: 12px;
  font-weight: bold;
  padding: 4px 8px;
  border-radius: 0 8px 0 0;
  z-index: 2;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.hexo-article-recommender-relation-tag.related {
  background-color: rgba(42, 170, 138, 0.9); /* 绿色:相关 */
}

.hexo-article-recommender-relation-tag.fallback {
  background-color: rgba(229, 80, 57, 0.9); /* 橙红:推荐 */
}

.hexo-article-recommender-post-card:hover .hexo-article-recommender-relation-tag {
  opacity: 1;
  transform: translateY(0);
}

/* 相关度分数样式 */
.hexo-article-recommender-relation-tag .hexo-article-recommender-score {
  font-size: 10px;
  opacity: 0.8;
  margin-left: 4px;
}

/* 响应式 */
@media (max-width: 768px) {
  .hexo-article-recommender-posts {
    flex-direction: column;
    align-items: center;
  }
  .hexo-article-recommender-post-card {
    width: calc(100% - 30px);
    max-width: 400px;
  }
}
</style>
{%- endif -%}

然后,在对应的post模板中(/themes/you-theme-name/layout/post.njk)找到想注入的地方(自己喜欢在哪就添加在哪)添加,其中include内容要指向上一步添加的recommended-posts.njk文件路径

  {% set cfg = config.article_recommender or {} %}
  {% set _ar_data = {
    posts: recommended_posts_full(page, cfg.default_count or 3),
    recommendedTitle: cfg.recommended_title or '推荐文章主题推荐'
  } %}
  {% set posts = _ar_data.posts %}
  {% set recommendedTitle = _ar_data.recommendedTitle %}
  {% include '_partials/recommended-posts.njk' %}
步骤三:启动服务

使用hexo cl && hexo g && hexo s查看是否生效

四、插件特性

上文中虽然在手动注入模块描述的比较详细,但是一般用户只要用最低的成本(一行命令安装),该插件就能提供可用的本地推荐体验。

完全本地化,无外部依赖无需调用任何 API 或第三方云服务,以本地优先/隐私优先。推荐卡片自带响应式设计、悬停动画、阅读时长、推荐分数、关系类型等标签,提供现代化的视觉体验。采用多维度融合推荐算法,综合考虑语义内容、标题关键词、标签、分类、时效性五大维度,并提供三种策略(balanced、title-focused、content-focused)供用户选择。

Hexo的文章推荐插件不多,希望hexo-article-recommender能给广大Hexo用户带来更好的选择。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐