SSG 预渲染工具站 SEO 优化实践:从零收录到全面修复的 7 个问题

_

概述

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 内容需执行 JSSEO 表现
CSR空 div + JS bundle依赖搜索引擎渲染队列,收录慢
SSR完整 HTMLSEO 友好,但服务器开销大
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/tax200200个税计算器页面 ✅
/fake-page-12345404200首页内容 ❌
/anything/random/url404200首页内容 ❌

搜索引擎蜘蛛发现大量 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/tax200内容 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 URLhttps://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 控制台层面处理,有三种方案:

方案操作个人偏好
修改回源 Hostwww 域名回源 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,绕过 CDNcurl -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 问题的时候需要记住:

  1. 分层验证:CDN 访问正常不代表源站正常,必须用 --resolve 直连源站
  2. 绕过缓存:用 Cache-Control: max-age=0 绕过 CDN 缓存看真实响应
  3. 模拟蜘蛛:用 User-Agent 模拟百度/Google/Bing 蜘蛛抓取
  4. 四端一致:canonical、sitemap、实际 URL、301 方向必须指向同一格式

最后

欢迎大家对我的工具站提出意见!https://miaokit.tech

从 Prompt 开始的 AI 之旅 2026-07-18

评论区