概述
SSG(Static Site Generation,静态站点生成)是当前前端工程化中常见的预渲染方案。与 CSR(客户端渲染)不同,SSG 在构建时为每个路由生成独立的 HTML 文件,搜索引擎无需执行 JavaScript 即可获取完整内容。
理论上 SSG 对 SEO 是天然友好的。
我开发的工具站技术选型是 Vue3 + vite-plugin-prerender + OpenResty + 腾讯云 CDN ,包含 20+ 个在线工具(个税计算、房贷计算、BMI 等)。站点完成开发、域名备案、部署上线后,预期是能够获得自然搜索流量。
但实际情况是:上线后网站零收录、零流量。这并不是流量少的问题,而是百度和 Google 一条收录都没有,Bing Webmaster Tools 报告站点存在异常,但好赖是收录了一两条。
在经过系统性排查后发现 7 个 SEO 问题,很零散的分布在 Nginx 配置、CDN 回源、端口监听、URL 规范化、Sitemap 规范、域名重定向、资源压缩等多个层面。这些问题在开发阶段不可见,只有在搜索引擎抓取时才会暴露。
本文记录了对这些SEO问题完整的排查与修复的过程。
SSG 与 SEO 的关系
我们先梳理三种渲染模式对 SEO 的影响:
| 模式 | HTML 内容 | 需执行 JS | SEO 表现 |
|---|---|---|---|
| CSR | 空 div + JS bundle | 是 | 依赖搜索引擎渲染队列,收录慢 |
| SSR | 完整 HTML | 否 | SEO 友好,但服务器开销大 |
| SSG | 完整 HTML(构建时生成) | 否 | SEO 友好,服务器开销最小 |
SSG 的 SEO 优势在于:构建时生成完整 HTML,蜘蛛抓取即可获取内容。但部署架构(Nginx + CDN)引入的配置问题,可能完全抵消 SSG 的 SEO 优势。
以下是验证 SSG 是否正常工作的方法:
# 检查 HTML 是否包含实际内容(而非空 div)
curl -s https://miaokit.tech/tool/tax | wc -c
# 如果 > 10KB,说明预渲染正常,HTML 包含完整内容
# 检查是否需要 JS 才能渲染内容
curl -s https://miaokit.tech/tool/tax | grep -c "实际关键词"
# 如果关键词出现,说明内容在 HTML 中,不需要 JS 执行
判断依据: 用
curl(不执行 JS)抓取页面,如果 HTML 中包含完整内容(文字、结构化数据),说明 SSG 正常。如果只有一个空的<div id="app">,说明预渲染未生效,退化为 CSR。
问题及解决方案
软 404
定义
软 404(Soft 404)是指服务器对不存在的 URL 返回 200 状态码而非 404,我们通常返回了首页或其他兜底内容。搜索引擎无法区分真实页面和垃圾 URL,会降权或停止收录整个站点。
这个问题是 SSG 部署最隐蔽的收录杀手
根因
SSG 项目在部署时,我们常直接复用 SPA 的 Nginx 配置:
# SPA 标准配置(SSG 不适用)
location / {
try_files $uri $uri/ /index.html;
}
设计这种配置的目标是:SPA 的所有路由由 JavaScript 渲染,Nginx 需要将所有请求兜底到 index.html,由前端路由处理。
但实际上 SSG 不需要这个兜底。SSG 的每个路由在构建时已经生成了独立的 HTML 文件:
dist/
├── index.html # 路由 /
├── tool/
│ ├── tax/index.html # 路由 /tool/tax/
│ └── bmi/index.html # 路由 /tool/bmi/
└── guide/
└── tax/index.html # 路由 /guide/tax/
Nginx 的 $uri/ 会自动匹配目录下的 index.html(由 index 指令处理),不需要 fallback。保留 /index.html fallback 后,所有不存在的路径都返回 200 + 首页内容。
影响分析
| URL | 预期状态码 | 实际状态码(SPA fallback) | 返回内容 |
|---|---|---|---|
| /tool/tax | 200 | 200 | 个税计算器页面 ✅ |
| /fake-page-12345 | 404 | 200 | 首页内容 ❌ |
| /anything/random/url | 404 | 200 | 首页内容 ❌ |
搜索引擎蜘蛛发现大量 URL 返回 200 + 相同内容,判定为内容农场或垃圾站点,就会停止收录,甚至可能打压。
修复
# SSG 正确配置
location / {
try_files $uri $uri/ =404;
}
将 /index.html 替换为 =404,找不到文件时直接返回 404 状态码。
指令解析:
$uri:精确匹配文件,如/sitemap.xml$uri/:匹配目录,由index指令查找index.html=404:都不匹配时返回 404
验证
# 不存在的路径必须返回 404
curl -o /dev/null -w "%{http_code}" https://miaokit.tech/fake-page-12345
# 输出:404 ✅
# 真实页面必须返回 200
curl -o /dev/null -w "%{http_code}" https://miaokit.tech/tool/tax
# 输出:200 ✅
注意: 我这里接入了 CDN ,所以在修改 Nginx 配置后,必须刷新 CDN 缓存。CDN 可能已经缓存了旧的 200 响应,不刷新则外部访问仍然拿到软 404。
但验证时使用-H "Cache-Control: max-age=0"绕过 CDN 缓存,不必每次都要去刷新。
CDN 回源协议
问题现象
修复软 404 并刷新 CDN 后,所有子页面返回 301 而非 200:
注意:下面的命令需要在你的服务主机上运行,否则你需要移除末尾的:443:127.0.0.1,下同。
# 外部访问(经 CDN)
curl -I https://miaokit.tech/tool/tax
# HTTP/2 301 Location: https://miaokit.tech/tool/tax ❌
# 直连源站 443
curl -I https://miaokit.tech/tool/tax -k --resolve miaokit.tech:443:127.0.0.1
# HTTP/2 200 ✅
根因分析
逐端口测试源站:
# 端口 80(HTTP)
curl -I http://miaokit.tech/tool/tax --resolve miaokit.tech:80:127.0.0.1
# HTTP/1.1 301 Location: https://miaokit.tech/tool/tax ❌
# 端口 443(HTTPS)
curl -I https://miaokit.tech/tool/tax -k --resolve miaokit.tech:443:127.0.0.1
# HTTP/2 200 ✅
源站在 80 端口配置了 HTTP → HTTPS 强制跳转(1Panel/宝塔的「强制 HTTPS」功能),而 CDN 回源走的是 HTTP 协议。
数据流:
CDN 回源请求(HTTP, 端口 80)
→ 源站 Nginx 收到 HTTP 请求
→ Nginx 强制 HTTPS 跳转,返回 301
→ CDN 收到 301,缓存 301
→ 后续所有请求返回 301 ❌
CDN 是为了回源获取内容并缓存,但源站的强制跳转使 CDN 拿到的不是内容而是重定向。CDN 将 301 缓存后,即使源站修复,用户访问仍然拿到 301。
修复
在 CDN 控制台修改回源协议为 HTTPS:
CDN 控制台 → 域名管理 → 回源配置
回源协议:HTTP → HTTPS
回源端口:443
修改后刷新 CDN 缓存。
冲突分析:**
**源站强制 HTTPS 与 CDN HTTP 回源是互斥的。有两种解法:
(1)CDN 回源改为 HTTPS(推荐);
(2)关闭源站强制 HTTPS 跳转,由 CDN 层面处理 HTTP → HTTPS。
方案 2 的问题是需要确保 CDN 自身正确配置了 HTTPS,否则用户可能通过 HTTP 访问。
验证
# 绕过 CDN 缓存验证
curl -I -H "Cache-Control: max-age=0" https://miaokit.tech/tool/tax
# HTTP/2 200 ✅
# 直连源站验证
curl -I https://miaokit.tech/tool/tax -k --resolve miaokit.tech:443:127.0.0.1
# HTTP/2 200 ✅
源站监听非 80/443 端口
问题现象
CDN 访问正常,但百度搜索资源平台的「抓取诊断」报错:
抓取网址:https://miaokit.tech/tool/bmi
网站IP:212.xxx.xxx.183(源站 IP)
返回HTTP头:HTTP/2 404
抓取异常信息:找不到页面 ❌
根因分析
搜索引擎蜘蛛在某些情况下会直接解析域名 DNS 获取源站 IP,绕过 CDN 直连源站 443 端口进行抓取。如果源站的 Nginx 配置中没有监听 443 端口的对应 server block,请求会走到默认 server block(00.default.conf),返回 404。
检查 Nginx 配置:
server {
listen 8085; // 只有 8085
server_name miaokit.tech;
root /www/sites/miaokit.tech/index;
// ...
}
此 server block 只监听 8085,百度蜘蛛直连 443 时,没有匹配到 server_name miaokit.tech 的 443 配置,走到默认配置返回 404。
验证方法
使用 --resolve 参数模拟蜘蛛直连源站 IP:
# 正确的测试方法:指定正确的 Host 头
curl -I https://miaokit.tech/tool/bmi -k --resolve miaokit.tech:443:127.0.0.1
# Host: miaokit.tech → 匹配到正确的 server block → 200 ✅
# 错误的测试方法:Host 头为 localhost
curl -I https://localhost/tool/bmi -k
# Host: localhost → 不匹配 server_name → 走默认配置 → 404 ❌
注意:
curl ``https://localhost/的 Host 头是localhost,不匹配 Nginx 的server_name,会走到default_server。排查时必须用--resolve指定正确的域名,否则会误判问题。
修复
server {
listen 8085;
listen 80;
listen 443 ssl; // 新增 443 监听
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
server_name miaokit.tech www.miaokit.tech;
root /www/sites/miaokit.tech/index;
location / {
rewrite ^/(.+)/$ /$1 permanent;
index index.html;
try_files $uri $uri/index.html =404;
}
error_page 404 /404/index.html;
}
为什么 CDN 正常但源站不正常
CDN 和搜索引擎蜘蛛的抓取路径不同:
# CDN 回源路径
用户 → CDN 边缘节点 → CDN 回源(指定端口/协议)→ 源站指定端口
# CDN 回源端口可以是 8085、80 或 443,取决于 CDN 配置
# 搜索引擎蜘蛛直连路径
蜘蛛 → DNS 解析获取源站 IP → 直连源站 IP:443 → 源站 443 端口
# 蜘蛛默认走 443,不经过 CDN
因此 CDN 能正常访问不等于源站能被蜘蛛正常抓取。必须单独验证源站 443。
URL 规范化
问题
SSG 预渲染生成的目录结构,导致同一内容有两个可访问 URL:
| URL | 状态码 | 内容 | 问题 |
|---|---|---|---|
| /tool/tax | 200 | 内容 A | — |
| /tool/tax/ | 200 | 内容 A(相同) | 重复内容 |
Nginx 的 index 指令会自动处理目录访问:/tool/tax/ → 找到 /tool/tax/index.html。但 /tool/tax(无斜杠)也会匹配到目录,两个 URL 返回相同内容。
影响
- 搜索引擎判定为重复内容,两个 URL 互相竞争排名。
- 如果 canonical 指向无尾斜杠版本,但带尾斜杠版本也能访问,搜索引擎会收到矛盾信号。
修复
统一 URL 格式,使用 301 重定向消除重复:
location / {
# 带尾斜杠 → 301 → 无尾斜杠
# /tool/tax/ → /tool/tax
# / 不受影响(不匹配 ^/(.+)/$)
rewrite ^/(.+)/$ /$1 permanent;
index index.html;
try_files $uri $uri/index.html =404;
}
正则 ^/(.+)/$ 解析:
^/:以/开头(.+)/:捕获一个或多个字符,以/结尾$:字符串结束/$1:重定向到捕获的内容(去掉尾部/)permanent:301 永久重定向
canonical 一致性
需要四个层面保持一致(必须):
| 层面 | URL 格式 |
|---|---|
| 实际访问 URL | 无尾斜杠:/tool/tax |
| 301 重定向方向 | /tool/tax/ → 301 → /tool/tax |
| canonical 标签 | <link rel="canonical" href="https://miaokit.tech/tool/tax"> |
| sitemap URL | https://miaokit.tech/tool/tax |
一致性原则: canonical、sitemap、实际 URL、301 重定向方向,四者必须指向同一格式。任何不一致都会导致搜索引擎困惑,影响收录效率。Google Search Console 和 Bing Webmaster Tools 都会对这个问题发出警告。
Sitemap 规范
Sitemap 的作用
Sitemap.xml 向搜索引擎声明站点中所有可抓取的 URL,帮助蜘蛛发现和调度抓取。对于 SSG 站点,sitemap 通常在构建时自动生成。
常见问题
问题 1:缺少 lastmod
<!-- 缺少 lastmod -->
<url>
<loc>https://miaokit.tech/tool/tax</loc>
</url>
<!-- 正确格式 -->
<url>
<loc>https://miaokit.tech/tool/tax</loc>
<lastmod>2026-09-01</lastmod>
</url>
lastmod 告诉搜索引擎每个 URL 的最后更新时间,帮助蜘蛛判断是否需要重新抓取。缺失不会直接导致不收录,但会降低抓取效率。
问题 2:URL 格式与实际不一致
如果 sitemap 中的 URL 带尾斜杠,但实际 URL 不带(或反之),搜索引擎会跟随 sitemap 中的 URL,遇到 301 重定向后才能到达实际页面,增加抓取开销。
问题 3:Sitemap 本身返回非 200
Nginx 的 rewrite 规则可能误伤 sitemap.xml:
# rewrite ^/(.+)/$ /$1 permanent;
# 这条规则不会匹配 sitemap.xml(不以 / 结尾),但需确认
curl -I https://miaokit.tech/sitemap.xml
# 必须返回 200,不能是 301 或 404
这里提供一个我自己用来自动生成 lastmod 的脚本:
// generate-sitemap.js
// 在 prerender 构建后执行,读取 HTML 文件修改时间作为 lastmod
const fs = require('fs');
const path = require('path');
const routes = [
'/', '/tool/tax', '/tool/bmi', '/tool/mortgage',
'/guide', '/guide/tax', '/guide/bmi',
'/privacy', '/contact', '/help',
// ... 所有路由
];
const baseUrl = 'https://miaokit.tech';
const today = new Date().toISOString().split('T')[0];
const urls = routes.map(route => {
const filePath = path.join(__dirname, 'dist', route, 'index.html');
let lastmod = today;
if (fs.existsSync(filePath)) {
// 使用文件实际修改时间
lastmod = fs.statSync(filePath).mtime.toISOString().split('T')[0];
}
return ` <url>
<loc>${baseUrl}${route}</loc>
<lastmod>${lastmod}</lastmod>
</url>`;
}).join('\n');
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`;
fs.writeFileSync(path.join(__dirname, 'dist', 'sitemap.xml'), sitemap);
console.log(`Sitemap generated: ${routes.length} URLs`);
验证
# Sitemap 状态码
curl -o /dev/null -w "%{http_code}" https://miaokit.tech/sitemap.xml
# 输出:200
# URL 数量
curl -s https://miaokit.tech/sitemap.xml | grep -c "<loc>"
# 输出:57
# lastmod 数量(应与 URL 数量一致)
curl -s https://miaokit.tech/sitemap.xml | grep -c "<lastmod>"
# 输出:57
# robots.txt 中声明 sitemap
curl -s https://miaokit.tech/robots.txt | grep -i sitemap
# 输出:Sitemap: https://miaokit.tech/sitemap.xml
www 重定向
问题现象
Bing Webmaster Tools 报告站点存在 canonical/重复内容问题。
根因
# 非 www
curl -I https://miaokit.tech/
# HTTP/2 200 content-length: 45417 ✅
# www
curl -I https://www.miaokit.tech/
# HTTP/2 200 content-length: 130 ❌(不是 301 跳转)
# www 子页面
curl -I https://www.miaokit.tech/tool/tax
# HTTP/2 404 ❌
Nginx 配置中有 www → 非 www 的 301 规则:
if ($host = 'www.miaokit.tech') {
return 301 https://miaokit.tech$request_uri;
}
但 CDN 拦截了 www 域名,没有回源到 Nginx,直接返回了一个 130 字节的占位页面。Bing 认为存在两个独立的、内容不一致的网站。
修复
在 CDN 控制台层面处理,有三种方案:
| 方案 | 操作 | 个人偏好 |
|---|---|---|
| 修改回源 Host | www 域名回源 Host 设为 miaokit.tech,让 Nginx 301 规则生效 | 推荐 |
| CDN 配置 301 规则 | 在 CDN 控制台配置 URL 跳转规则 | 备选 |
| 删除 www 域名 | CDN 删除 www 域名配置,DNS 中给 www 加 CNAME 指向 miaokit.tech | 备选 |
验证
# www 必须返回 301
curl -I https://www.miaokit.tech/
# HTTP/2 301 Location: https://miaokit.tech/ ✅
# 非 www 必须返回 200
curl -I https://miaokit.tech/
# HTTP/2 200 ✅
资源压缩
问题
# CSS 有 brotli 压缩 ✅
curl -I -H "Accept-Encoding: gzip, deflate, br" https://miaokit.tech/assets/index.css
# content-encoding: br
# JS 有 brotli 压缩 ✅
curl -I -H "Accept-Encoding: gzip, deflate, br" https://miaokit.tech/assets/index.js
# content-encoding: br
# HTML 没有压缩 ❌
curl -I -H "Accept-Encoding: gzip, deflate, br" https://miaokit.tech/
# (无 content-encoding 头)
# content-length: 45417(未压缩)
CDN 的智能压缩通常默认只处理 JS/CSS 等静态资源,不包含 HTML。HTML 45KB 全量传输,开启 gzip 后可降至约 10KB。
修复
# Nginx 配置
gzip on;
gzip_types text/html text/plain text/css application/javascript;
gzip_min_length 1024;
gzip_comp_level 6;
# 或在 CDN 控制台的智能压缩中,将 text/html 加入压缩范围
影响
Bing Webmaster Tools 的 SEO 报告中包含页面性能评分,未压缩的 HTML 会降低性能分。这虽然不是收录的直接因素,但会影响页面体验评分。
排查方案总结
分层排查
SEO 问题涉及多个技术层面,必须逐层排查:
CDN 层
├── 回源协议(HTTP/HTTPS)
├── 回源端口(80/8085/443)
├── 缓存策略(是否缓存了错误响应)
├── 压缩配置(HTML/JS/CSS)
└── www 域名处理(是否回源到 Nginx)
Nginx 层
├── 端口监听(443 是否有对应 server block)
├── try_files 配置(SSG 不需要 SPA fallback)
├── rewrite 规则(尾斜杠 301)
├── gzip 配置
└── error_page 配置
应用层
├── prerender 输出(HTML 是否包含完整内容)
├── canonical 标签(URL 格式是否一致)
├── sitemap(lastmod、URL 格式)
├── robots.txt(是否声明 sitemap)
└── 结构化数据(JSON-LD)
站长平台层
├── 站点验证(meta/文件/CNAME)
├── sitemap 提交
├── 抓取诊断(模拟蜘蛛抓取)
└── URL 请求收录
关键排查工具
| 工具 | 用途 | 命令 |
|---|---|---|
| curl -I | 检查 HTTP 状态码和响应头 | curl -I ``https://miaokit.tech/tool/tax |
| curl --resolve | 直连源站 IP,绕过 CDN | curl -I ``https://miaokit.tech/`` -k --resolve miaokit.tech:443:127.0.0.1 |
| curl -H "Cache-Control: max-age=0" | 绕过 CDN 缓存 | curl -I -H "Cache-Control: max-age=0" ``https://miaokit.tech/ |
| grep -oP | 检查 HTML 内容中的关键词 | ` curl -s https://miaokit.tech/tool/tax |
| wc -c | 检查 HTML 文件大小 | ` curl -s https://miaokit.tech/ |
| 百度抓取诊断 | 模拟百度蜘蛛直连源站 | 百度搜索资源平台 → 抓取诊断 |
| Bing Webmaster Tools | 检测 canonical/重复内容 | Bing Webmaster → SEO 报告 |
| Google Search Console | 检查收录状态 | search.google.com/search-console |
常见误区
误区 1:CDN 正常 = 源站正常
CDN 缓存了正确响应后,会掩盖源站的问题。必须用 --resolve 直连源站验证。
误区 2:curl localhost = 测试源站
curl ``https://localhost/ 的 Host 头是 localhost,不匹配 Nginx server_name,会走默认配置。必须用 --resolve 指定正确域名。
误区 3:SSG 配置可以照抄 SPA
SSG 和 SPA 的 Nginx 配置核心区别在 try_files:SPA 需要 /index.html fallback,SSG 需要 =404。
快速检查
可以使用下面的 curl 命令快速检查自己的网站是否有相关的问题(记得将域名替换成自己的域名,在自己的服务主机上运行命令)。
# 1. 软 404 检查
curl -o /dev/null -w "%{http_code}" https://miaokit.tech/fake-page-12345
# 期望:404
# 2. CDN 回源协议
curl -I -H "Cache-Control: max-age=0" https://miaokit.tech/tool/tax
# 期望:200
# 3. 源站 443 可达
curl -I https://miaokit.tech/tool/tax -k --resolve miaokit.tech:443:127.0.0.1
# 期望:200
# 4. 尾斜杠 301
curl -o /dev/null -w "%{http_code} %{redirect_url}" https://miaokit.tech/tool/tax/
# 期望:301 https://miaokit.tech/tool/tax
# 5. canonical 一致性
curl -s https://miaokit.tech/tool/tax | grep canonical
# 期望:href 与实际 URL 格式一致
# 6. sitemap lastmod
curl -s https://miaokit.tech/sitemap.xml | grep -c "<lastmod>"
# 期望:等于 URL 数量
# 7. www 重定向
curl -o /dev/null -w "%{http_code}" https://www.miaokit.tech/
# 期望:301
# 8. HTML 压缩
curl -I -H "Accept-Encoding: gzip, deflate, br" https://miaokit.tech/ | grep content-encoding
# 期望:gzip 或 br
# 9. robots.txt
curl -s https://miaokit.tech/robots.txt
# 期望:Allow: / + Sitemap 声明
# 10. 蜘蛛模拟
curl -I -H "User-Agent: Mozilla/5.0 (compatible; Baiduspider/2.0)" https://miaokit.tech/tool/tax
curl -I -H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1)" https://miaokit.tech/tool/tax
curl -I -H "User-Agent: Mozilla/5.0 (compatible; bingbot/2.0)" https://miaokit.tech/tool/tax
# 期望:全部 200
总结
使用 SSG 在构建层面为 SEO 提供了良好基础,让每个路由都有完整 HTML,也让搜索引擎无需执行 JavaScript 即可获取内容。
但我部署时的配置问题,完美的让我得到了一个零流量的网站(我真是要哭死了)。在这次做 SEO 优化的过程中发现的 7 个问题主要在三个层面(基本沾完了):
- CDN 层:回源协议错误、www 域名未回源、HTML 未压缩
- Nginx 层:SPA fallback 导致软 404、443 端口未监听、尾斜杠未统一
- 应用层:sitemap 缺少 lastmod、canonical 与实际 URL 不一致
每个问题单独看都不复杂,但它们叠加在一起时,表现出的症状(零收录、零流量)让我无从下手。我这次遇到的所有问题的核心是 CDN 缓存掩盖了源站问题,使得从外部观察到的行为与源站实际行为不一致。
所以我们在排查 SEO 问题的时候需要记住:
- 分层验证:CDN 访问正常不代表源站正常,必须用
--resolve直连源站 - 绕过缓存:用
Cache-Control: max-age=0绕过 CDN 缓存看真实响应 - 模拟蜘蛛:用 User-Agent 模拟百度/Google/Bing 蜘蛛抓取
- 四端一致:canonical、sitemap、实际 URL、301 方向必须指向同一格式
最后
欢迎大家对我的工具站提出意见!https://miaokit.tech