为你的Hexo博客添加“相关文章”模块
都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)的占比如下:
| 策略名称 | 语义占比 | 标题占比 | 标签占比 | 分类占比 | 时效占比 |
|---|---|---|---|---|---|
|
| 50% | 20% | 15% | 10% | 5% |
|
| 30% | 40% | 15% | 10% | 5% |
|
| 70% | 10% | 10% | 5% | 5% |
如果你还有什么好的占比建议可以直接在评论区或者在github的开源项目上留言
3.2. 手动注入(高级)
自动注入(默认):开启 inject: true 即可。
该插件通常通过 Hexo 的 injects 机制,将推荐内容注入到主题模板中的 </article> 标签之后。但是如果遇到</article> 标签之后不适用,或者在该位置的背景颜色与插件默认的颜色不搭,可以尝试手动注入模板。
步骤一:关闭自动注入
在博客的根目录的_config.yml中关闭并配置自动注入选项:
article_recommender:
inject: false
步骤二:创建并引入独立的推荐模板文件(示例)
EJS 模板(如 landscape、fluid 等主题)
在你的主题目录下(如 /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主题等)
以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用户带来更好的选择。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)