背景
初衷是提升个人网站:ynsstudy.cn对浏览器的检索程度,但是当具体操作时发现传统客户端渲染 (CSR) 的劣势开始显现:搜索引擎爬虫通常只能抓取到初始的空 <div id="app"></div> 容器,无法有效收录通过 JavaScript 异步加载的文章内容。
方案选型
在方案调研阶段,主要评估了以下两种路径:
1.彻底重构为 SSR (Nuxt.js):
SEO 效果最佳,但需要重构现有的 Vue 路由、状态管理(将 Pinia 中的 Token 存储从 localStorage 迁移至 Cookie),并且需要单独处理强依赖浏览器环境的组件(如富文本编辑器)。
2.自建无头浏览器服务:
在 Java 后端引入 Playwright 服务端渲染。但考虑到当前服务器(4G内存)由于运行微服务,可用内存已不足 300MB,引入完整的浏览器内核会导致全面崩盘的风险(主要原因还是服务器太辣鸡)。
综合考虑迁移成本、业务需求(仅首页和个人主页强依赖 SEO)以及服务器资源瓶颈,当然还有一部分原因是本人对Nuxt.js了解的真是少之又少,最终选择了侵入性最小的动态渲染 (Dynamic Rendering) 方案:结合 Nginx 的 User-Agent 识别与第三方预渲染服务 (Prerender.io)。
实施过程
1. Nginx 层流量分发配置
核心思路是在网关层进行流量鉴别。当普通用户访问时,Nginx 返回 Vue 编译后的静态文件,保持原有的无刷新体验;当识别到爬虫访问时,将其代理至预渲染服务,由预渲染服务在云端执行完 JS 并组装 HTML 后返回给爬虫。
需要注意的是,在 Nginx 配置语法中,proxy_set_header 指令不能直接放置在 if 块内部,需将其提取到外部层级。核心配置节选如下:
location @prerender {
set $prerender 0;
# 识别主流搜索引擎爬虫
if ($http_user_agent ~* "baiduspider|googlebot|bingbot|yandex|sogou|spider") {
set $prerender 1;
}
# 静态资源排除预渲染
if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
set $prerender 0;
}
resolver 8.8.8.8 114.114.114.114;
proxy_set_header X-Prerender-Token 'YOUR_TOKEN_HERE';
if ($prerender = 1) {
rewrite .* /$scheme://$host$request_uri? break;
proxy_pass http://service.prerender.io;
}
if ($prerender = 0) {
root /var/www/vue_dist;
rewrite .* /index.html break;
}
}2. 动态修改title标签
放弃了使用原生 JavaScript 直接操作 document.title 或 document.createElement 的方式。引入 @vueuse/head 进行响应式头部管理。在接口数据返回后,直接更新响应式变量,组件卸载时会自动清理残留的 Meta 标签。
3. 预渲染时序控制(防空壳抓取)
实施初期遇到了爬虫抓取过快,页面异步数据还未挂载就被截图存成空壳的问题(导致功能实现了,但是核心诉求并没有得到解决)。
解决方案是引入预渲染服务的全局状态锁。在 index.html 头部声明 window.prerenderReady = false;,等待 Vue 组件数据请求完成且 DOM 更新后(nextTick),再将其置为 true,通知云端浏览器可以开始提取快照。
4. DOM 结构改造与内链建设
网站中常规的页面跳转多通过 @click="router.push(...)" 实现,但这会导致爬虫由于不识别 JS 事件而无法进行深度抓取。
为了构建健康的内部链接网,对文章列表页进行了渐进式增强:使用 <a> 标签包裹可点击区域,配置相对路径的 href 供爬虫识别,同时配合 Vue 的 @click.prevent 修饰符拦截默认的页面跳转,触发原有的弹窗阅读逻辑。此举在不破坏原有交互的前提下,提供了完整的爬虫抓取路径。
<a :href="'/oneBlog/' + blog.GUID" @click.prevent class="seo-link">
<h4 class="blog-title">{{ blog.BLOG_TITLE }}</h4>
</a>5. Sitemap 接口性能优化
在生成供搜索引擎主动抓取的站点地图时,原有的 Java 代码存在将全量数据加载到内存并通过 StringBuilder 拼接的问题,这在数据量增大时极易引发内存溢出。
对代码进行了如下重构:
- 废弃 StringBuilder,改为 response.getWriter().print() 流式输出,大幅降低 JVM 内存占用。
- 增加了 XML 保留字符(如 &、<)的转义处理,防止 XML 解析异常。
- 拉开权重梯度:将核心文章详情的 <priority> 设为 0.8,个人主页权重下调至 0.5,引导爬虫资源向核心内容倾斜。
总结
对于部分页面依赖 SEO,且服务器资源有限的项目,基于网关拦截的动态渲染是一个具有较高性价比的技术决策。它避免了全盘推翻现有前端架构,同时也能满足搜索引擎收录的核心诉求。