前言 上一篇《Hexo 博客三项基础修复》解决了 RSS、Sitemap、评论系统的高优先级硬性问题,博客具备了稳固的基础设施。但 Lighthouse 审查还暴露了更多问题:
类别
问题数
严重程度
SEO
4 项
🟡 中
体验
7 项
🟡 中
品牌
2 项
🟡 中
其中 SEO 问题影响搜索引擎发现和社交分享展示,体验问题影响读者停留和互动,品牌问题则关系到博客的识别度和专业性。
本文将依次记录这三个方面共 13 项优化的完整过程。
一、SEO 优化 问题 : 文章分享到微信、Twitter 时没有标题、描述、缩略图,只有光秃秃的 URL。
方案 : NexT 主题已集成 Hexo 内置的 open_graph() 方法,在 head-unique.swig 中调用:
它自动从页面 front-matter 提取 title、description、image 生成 OG 标签:
1 2 3 <meta property ="og:title" content ="文章标题" > <meta property ="og:description" content ="文章摘要" > <meta property ="og:image" content ="文章首图 URL" >
无需手动配置。如果需要自定义,Hexo 支持在 _config.yml 中传参:
1 2 3 4 open_graph: twitter_card: summary_large_image twitter_id: "@your_id" fb_admins: your_id
1.2 JSON-LD 结构化数据 问题 : 搜索引擎不理解页面的结构和内容类型,无法生成 Rich Snippet(富摘要)。文章在搜索结果页只能显示标题和链接,没有作者、发布时间等增强信息。
方案 : 编写了一个 json-ld.js helper,在 head-unique.swig 中调用 {{ json_ld() }},根据页面类型输出三组结构化数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 hexo.extend .helper .register ('json_ld' , function ( ) { var siteTitle = this .config .title ; var siteUrl = this .config .url ; var blocks = []; if (this .page .__index ) { blocks.push ({ '@context' : 'https://schema.org' , '@type' : 'WebSite' , name : siteTitle, url : siteUrl, potentialAction : { '@type' : 'SearchAction' , target : siteUrl + '/search.json?q={search_term_string}' , 'query-input' : 'required name=search_term_string' } }); } if (this .page .layout === 'post' ) { blocks.push ({ '@context' : 'https://schema.org' , '@type' : 'BlogPosting' , headline : this .page .title , description : this .page .description , datePublished : this .page .date .format (), dateModified : (this .page .updated || this .page .date ).format (), author : { '@type' : 'Person' , name : this .config .author }, publisher : { '@type' : 'Organization' , name : this .config .title } }); } blocks.push ({ }); return blocks.map (function (b ) { return '<script type="application/ld+json">' + JSON .stringify (b) + '</script>' ; }).join ('\n' ); });
完成后用 Google 的 Rich Results Test 验证,确认 BlogPosting、BreadcrumbList 结构正确。
1.3 统计系统接入 问题 : 博客上线后没有任何访问数据,无法了解读者来源、热门内容。
方案 : NexT 主题内置了 Google Analytics 和百度统计的注入模板,只需配置 tracking_id:
1 2 3 4 5 6 google_analytics: tracking_id: G-XXXXXXXXXX only_pageview: false baidu_analytics: your_baidu_hm_code
配置后自动在 <head> 中注入 gtag.js 脚本。only_pageview: false 表示加载完整 gtag,支持事件追踪和用户分析。如果只想统计 PV,设为 true 会改用 sendBeacon 轻量推送。
1.4 自动描述生成 问题 : 91 篇文章,手动写 description 太耗时。没有 description 的页面在搜索引擎中摘要为空,影响点击率。
方案 : 写了一个 auto-description.js 过滤器,在 after_post_render 阶段自动提取前 200 字符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 hexo.extend .filter .register ('after_post_render' , function (data ) { if (data.description && data.description .trim ()) return data; var plainText = (data.content || data._content || '' ) .replace (/<[^>]+>/g , '' ) .replace (/```[\s\S]*?```/g , '' ) .replace (/[#*>`\-|~_\n\r\t\[\]]/g , ' ' ) .replace (/\s{2,}/g , ' ' ) .trim (); var description = plainText.substring (0 , 200 ).trim (); if (description) { data.description = description + (plainText.length > 200 ? '...' : '' ); } return data; });
规则:
已手动填写 description 的文章不覆盖
去掉 HTML 标签和代码块
去掉 Markdown 语法符号
截取前 200 字符,超出加 ...
这样所有页面都自动有了 SEO 友好的元描述。
二、用户体验优化 2.1 自定义 404 页面 问题 : GitHub Pages 默认 404 页面太简陋,访客遇到死链直接离开。
方案 : 创建 source/404.md,使用 NexT 页面模板渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --- title: 页面未找到 layout: page permalink: /404.html --- <div class ="error-page" > <div class ="error-code" > 404</div > <div class ="error-message" > 鱼跑路了...</div > <p > 您要找的页面可能已经被鱼叼走了</p > <div class ="error-actions" > <a href="/" class="btn-error">回到首页</a> <a href="/archives/" class="btn-error">浏览文章</a> <a href="/fish/" class="btn-error">看鱼去</a> </div> </div>
加上品牌色样式的按钮(#37c6c0),hover 时上浮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .error-page text-align : center padding : 60px 20px .error-code font-size : 100px color : #37c6c0 .btn-error display : inline-block padding : 10px 24px border-radius : 24px background : #37c6c0 color : #fff &:hover transform : translateY (-2px )
2.2 阅读时间估算 问题 : 读者打开文章时不知道大概需要多久能读完,容易没有心理预期。
方案 : 安装 hexo-symbols-count-time 插件,自动计算字数和预估阅读时间:
1 npm install hexo-symbols-count-time --save
主题配置中启用:
1 2 3 4 5 symbols_count_time: separated_meta: true item_text_post: true item_text_total: false
效果:文章标题下方显示 📄 字数: 2,350 / 🕐 阅读时间 ≈ 12 分钟。底部全站统计显示博客总字数。
2.3 相关文章推荐 问题 : 读者看完一篇文章后,不知道还有什么相关内容可以看,缺少停留引导。
方案 : 在 after_post_render 过滤器中实现标签匹配算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 hexo.extend .filter .register ('after_post_render' , function (data ) { if (!data.tags || !data.tags .length ) return data; var allPosts = hexo.locals .get ('posts' ); var currentTags = data.tags .map (function (t ) { return ((t && t.name ) || t || '' ).toLowerCase (); }); var scored = []; allPosts.data .forEach (function (p ) { if (p._id === data._id ) return ; var score = 0 ; var postTags = (p.tags && p.tags .data ) || p.tags || []; for (var i = 0 ; i < postTags.length ; i++) { var tag = postTags[i]; if (!tag) continue ; var tagName = ((tag.name || tag) + '' ).toLowerCase (); if (currentTags.indexOf (tagName) >= 0 ) score++; } if (score > 0 ) scored.push ({ post : p, score : score }); }); scored.sort (function (a, b ) { return b.score - a.score || b.post .date - a.post .date ; }); var html = '<div class="related-posts">...' ; scored.slice (0 , 6 ).forEach (function (item ) { }); data.content += html; });
卡片展示效果:使用 CSS Grid 自适应布局,悬停时边框变为品牌色并轻微上浮。
2.4 文章系列导航 问题 : 像「C# 学习笔记」这种系列文章有 22 篇,读者不知道当前读到第几篇、前后是什么。
方案 : 通过自定义标签 series:xxx 标记同系列文章,按日期排序,自动生成导航:
1 2 3 4 5 tags: - series:C#学习笔记 - C# - 委托
过滤器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 hexo.extend .filter .register ('after_post_render' , function (data ) { if (!data.tags || !data.tags .length ) return data; var seriesTagObj = null ; data.tags .forEach (function (t ) { if (t && t.name && String (t.name ).startsWith ('series:' )) { seriesTagObj = t; } }); if (!seriesTagObj) return data; var seriesName = String (seriesTagObj.name ).replace ('series:' , '' ); var seriesPosts = seriesTagObj.posts .toArray ().sort (function (a, b ) { return a.date - b.date ; }); var currentIndex = -1 ; for (var i = 0 ; i < seriesPosts.length ; i++) { if (seriesPosts[i]._id === data._id ) { currentIndex = i; break ; } } data.content += navHtml; });
踩坑记录 :data.tags 在 after_post_render 阶段是 _Query 对象,不是普通数组,Array.from() 会返回全 undefined。必须使用 forEach() 原生迭代。另外,用 TagModel.findOne({name: tagName}) 查询含冒号的 tag name 会失败,需要直接用 tag 对象上携带的 .posts 属性。
2.5 置顶功能 问题 : 首页按时间倒序显示文章,重要公告无法置顶。
方案 : 前置过滤器和后置渲染器配合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 hexo.extend .filter .register ('before_generate' , function ( ) { var posts = hexo.locals .get ('posts' ); posts.data .sort (function (a, b ) { var aTop = Number (a.top || a.sticky || 0 ); var bTop = Number (b.top || b.sticky || 0 ); if (aTop && !bTop) return -1 ; if (!aTop && bTop) return 1 ; if (aTop && bTop) return bTop - aTop; return b.date - a.date ; }); }); hexo.extend .filter .register ('after_post_render' , function (data ) { if (!data.top && !data.sticky ) return data; var notice = '<div class="post-pinned-notice">📌 置顶文章</div>' ; data.content = notice + data.content ; });
在文章 front-matter 中加一行即可置顶:
2.6 标签云 / 分类可视化 问题 : /tags/ 页面只有列表,看不出哪些标签文章多。
方案 : NexT 主题已使用 Hexo 内置的 tagcloud() 方法,按文章数自动调整字号和颜色:
1 2 3 4 5 6 7 tagcloud: min: 12 max: 30 start: "#ccc" end: "#111" amount: 200
渲染代码在 page.swig 中:
1 2 3 4 5 6 7 8 9 10 {%- if page.type === 'tags' %} {{ tagcloud({ min_font : theme.tagcloud.min, max_font : theme.tagcloud.max, amount : theme.tagcloud.amount, color : true, start_color: theme.tagcloud.start, end_color : theme.tagcloud.end }) }} {%- endif %}
分类页面同理,使用 list_categories()。
2.7 滚动动画 问题 : 页面切换和内容出现过于生硬。
方案 : NexT 主题内置 Velocity.js 动画引擎,开箱即用:
1 2 3 4 5 6 7 8 9 10 motion: enable: true async: false transition: post_block: fadeIn post_header: slideDownIn post_body: slideDownIn coll_header: slideLeftIn sidebar: slideUpIn
效果:页面加载时文章块淡入、侧边栏滑入。async: false 确保动画按序播放。
三、品牌视觉重构 3.1 品牌色系统 问题 : 博客颜色来自 NexT 默认灰蓝,到处都是标准 Bootstrap 风格,没有辨识度。
方案 : 在 source/_data/variables.styl 中定义品牌色体系,覆盖 NexT 内置变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $brand -primary = #37c6c0 $brand -secondary = #ff6b6b $brand -accent = #ffd93d $brand -success = #6bcb77 $brand -link = #4d96ff $blue = $brand -primary$blue -hover = darken ($brand -primary, 10% )$link -color = $brand -link$link -hover-color = $brand -secondary$btn -default-bg = transparent$btn -default-color = $brand -primary$btn -default-border = $brand -primary$border -radius-inner = 20px 20px 20px 20px $border -radius = 20px
这套变量通过 NexT 的 custom_file_path 注入机制自动生效。覆盖原理:source/_data/variables.styl 在主题 Stylus 编译开始时加载,后续所有引用 $blue、$link-color 的地方都会解析为我们的品牌色。
实际影响:
代码高亮的蓝色 → 青蓝 #37c6c0
文章链接色 → #4d96ff,hover → #ff6b6b
按钮边框和文字 → 品牌色
全局圆角 → 20px(原本是直角)
踩坑记录 :品牌色还在 styles.styl 中被多处直接引用(比如 404 页面的 .error-code、置顶标识 .post-pinned-notice、系列导航 .series-nav 的边框),这些不会自动被变量覆盖,需要手动更新。建议新样式统一使用变量,已有样式逐步迁移。
3.2 Hero 区域 问题 : 首页打开直接是文章列表,没有品牌展示区。新访客无法快速了解”这个博客是做什么的”。
方案 : 在首页顶部增加 Hero 展示区,包含四个部分:
① 模板 (themes/next/layout/_partials/hero.swig):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <div class="hero-section"> <div class="hero-content"> <h1 class="hero-title">🎣 ByteFisher</h1> <div class="hero-subtitle"> <span class="hero-prefix">钓鱼爱好者的</span> <span class="hero-typing" id="hero-typing"></span> <span class="hero-cursor">|</span> </div> <p class="hero-desc">分享游戏开发技术与钓鱼乐趣</p> <div class="hero-stats"> <div class="hero-stat"> <span class="hero-stat-value">{{ site.posts.length }}</span> <span class="hero-stat-label">文章</span> </div> <div class="hero-stat"> <span class="hero-stat-value">{{ theme.fishAlbumCount or 0 }}</span> <span class="hero-stat-label">鱼获</span> </div> <div class="hero-stat"> <span class="hero-stat-value">{{ theme.aigamesCount or 0 }}</span> <span class="hero-stat-label">小游戏</span> </div> </div> <div class="hero-actions"> <a href="/archives/" class="hero-btn hero-btn-primary">📚 浏览文章</a> <a href="/fish/" class="hero-btn hero-btn-secondary">🎣 钓鱼相册</a> <a href="/ai-games/" class="hero-btn hero-btn-secondary">🎮 玩小游戏</a> </div> </div> </div>
② 引用 (themes/next/layout/index.swig):
1 2 3 4 {% block content %} {% include '_partials/hero.swig' %} {%- for post in page.posts.toArray() %} ...
③ 打字机动画 (source/_data/body-end.swig):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 (function ( ) { function initHeroTyping ( ) { var el = document .getElementById ('hero-typing' ); if (!el) return ; var texts = ['编程世界' , '数字乐园' , '钓鱼天地' , '技术博客' ]; var textIndex = 0 , charIndex = 0 , isDeleting = false ; function type ( ) { var current = texts[textIndex]; el.textContent = isDeleting ? current.substring (0 , charIndex--) : current.substring (0 , charIndex++); if (!isDeleting && charIndex === current.length ) { setTimeout (function ( ) { isDeleting = true ; }, 2000 ); } else if (isDeleting && charIndex === 0 ) { isDeleting = false ; textIndex = (textIndex + 1 ) % texts.length ; } setTimeout (type, isDeleting ? 50 : 100 ); } type (); } if (document .readyState === 'loading' ) { document .addEventListener ('DOMContentLoaded' , initHeroTyping); } else { initHeroTyping (); } })();
核心机制:四个标语循环切换,先逐字打出 → 停留 2 秒 → 逐字删除 → 切换到下一个。打字速度 100ms/字,删除速度 50ms/字,光标 0.8s 闪烁。
④ 样式 (source/_data/styles.styl):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 .hero-section text-align : center padding : 60px 20px 40px background : linear-gradient (135deg , rgba (55 ,198 ,192 ,0.08 ), rgba (255 ,107 ,107 ,0.05 )) border-radius : 20px margin-bottom : 30px .hero-title font-size : 48px font-weight : 800 .hero-stats display : flex justify-content : center gap : 40px .hero-stat-value font-size : 32px font-weight : 700 color : #37c6c0 .hero-actions .hero-btn border-radius : 40px transition : all 0.3s &:hover transform : translateY (-2px ) .hero-btn-primary background : linear-gradient (135deg , #37c6c0 , #2da8a3 ) color : #fff @keyframes blink 0% , 50% { opacity : 1 } 51% , 100% { opacity : 0 }
关于数据统计 :theme.fishAlbumCount 和 theme.aigamesCount 由 fish-albums.js 在 before_generate 阶段动态计算——前者统计 source/fish/ 下的子目录数,后者统计 source/ai-games/ 下的子目录数(排除 gamejs)。每次构建自动更新,无需手动维护。
效果:首页打开首先看到品牌名 → 打字机循环标语 → 文章/鱼获/小游戏实时统计 → 三个导航按钮,引导访客深入浏览。
四、总结 改造前后全景对比:
类别
改造前
改造后
SEO
无 OG/JSON-LD,无 Analytics,无 description
社交分享富卡片 + 结构化数据 + 自动描述 + 流量统计
体验
无 404 页面,无阅读时间,无相关文章/系列导航,无置顶
品牌化 404 + 阅读估算 + 相关推荐 + 系列导航 + 置顶公告
品牌
主题默认配色,首页直出文章列表
统一品牌色体系(5 色)+ Hero 展示区(打字机+统计+按钮)
全部 13 项优化涉及 15 个新增/修改文件,包括 6 个过滤器(related-posts.js、series-nav.js、pinned-posts.js、auto-description.js、fish-albums.js、json-ld.js)、2 个模板(hero.swig、404.md)和 3 个自定义样式/数据文件(variables.styl、styles.styl、body-end.swig)。
所有改动基于 Hexo 的插件和过滤器机制实现,不修改 NexT 主题核心文件,主题升级时只需重新注入自定义文件即可移植。