Search Console “页面”报告里出现 “Soft 404” —— 字面意思是”看起来是 404 但服务器返回 200”。Google 抓了页面,发现内容空空如也(或只有”未找到”、“暂无数据”之类的提示),但 HTTP 状态码却是 200 OK。它的处理是:当作 404 不收录,但显式标出 “Soft 404” 让你修。
这类问题不在内容质量评判范围,而是站点工程问题——服务端应该返回真 404 / 410,结果错给了 200。
常见原因
1. 空的 category / tag / 搜索结果页返回 200
最高频。比如 /tag/未使用的标签/ 没任何文章,但模板照样渲染了页眉页脚 + “暂无内容”——HTTP 200,body 几乎空。
如何判断:
curl -sI "https://yourdomain.com/tag/empty-tag/" | head -1
# 期望 404;实际是 HTTP/2 200 就是 Soft 404
2. 动态页面找不到对应内容时仍返回 200
// 错误实现
app.get('/article/:slug', async (req, res) => {
const article = await db.findOne({ slug: req.params.slug });
if (!article) {
res.render('not-found', { message: '文章不存在' }); // 200
return;
}
res.render('article', { article });
});
res.render('not-found') 默认返回 200。应该是 res.status(404).render('not-found')。
3. SPA 的 404 路由返回 index.html + 200
如果你部署到 Firebase / Netlify 用 SPA fallback:
{
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
}
所有 URL 都返回 index.html + 200,包括根本不存在的 URL。然后前端 router 显示 “404 Not Found”——但 HTTP 是 200。
4. 重定向到首页代替 404
// 错
app.get('*', (req, res) => res.redirect('/'));
URL 不存在时不该悄悄重定向到首页。Google 抓到一堆”虽然 URL 不同但内容都是首页”的页面,标 Soft 404 或 Duplicate。
5. 只有导航 / 页脚 / 广告,没有主内容
页面 HTML 加载完,body 里有 header、nav、sidebar、ads、footer——但 <main> 里只有”加载中”或空。
# 看 main 区域字数
curl -sL https://yourdomain.com/page | grep -oE '<main[^>]*>[\s\S]*</main>' | wc -c
# < 500 字符通常就是 Soft 404 候选
6. JS 渲染失败但 HTTP 200
如果你站靠 SSR 但 SSR 报错时降级返回空 HTML,Google 看到空白页。
最短修复路径
Step 1:导出 Soft 404 URL 清单做分类
Search Console → 页面 → 点 “Soft 404” → 导出 URL 列表。按 URL 模式分类:
/tag/* → 空 tag 页
/search?q=* → 0 结果搜索页
/article/* → 内容已删但 URL 还在
/products/* → 库存为 0 商品
每类的修法不一样。
Step 2:把”该 404”的情况改成真 404
// Express / Node
app.get('/article/:slug', async (req, res) => {
const article = await db.findOne({ slug: req.params.slug });
if (!article) {
return res.status(404).render('not-found', { message: '文章不存在' });
}
res.render('article', { article });
});
// Next.js App Router
import { notFound } from 'next/navigation';
export default async function ArticlePage({ params }) {
const article = await getArticle(params.slug);
if (!article) notFound(); // 返回 404
return <Article {...article} />;
}
// Astro
---
const article = await getArticle(Astro.params.slug);
if (!article) return new Response(null, { status: 404 });
---
Step 3:内容被删的 URL 用 410 Gone(更明确)
如果某 URL 是永久删除而非”暂时不存在”,用 410 更好:
res.status(410).send('This page has been permanently removed.');
Google 看到 410 会比 404 更快从索引移除。
Step 4:空 tag / 0 结果搜索页加 noindex 或 410
// 空 tag 页:noindex + 不进 sitemap
export default function TagPage({ posts }) {
if (posts.length === 0) {
return (
<>
<Helmet><meta name="robots" content="noindex,follow" /></Helmet>
<main>这个标签暂无文章</main>
</>
);
}
// ...
}
或更激进:直接 404,因为空 tag 页对用户也没价值。
Step 5:SPA 部署修分发层
如果你用 Firebase Hosting / Netlify / Vercel 的 SPA fallback:
// firebase.json
{
"hosting": {
"rewrites": [
{ "source": "/article/**", "function": "ssrArticle" },
{ "source": "**", "destination": "/index.html" }
]
}
}
对 /article/** 这种需要真 404 的路径走 server function,让它能返回 404 状态码。其余 SPA 路由保留 200 fallback。
或者前端 router 在 404 时改成:
// 检测后通知 server 返回 404
// 但这只在 SSR 场景下行得通;CSR 没办法改 HTTP 状态
Step 6:部署后用 curl 验证 + Search Console 重新验证
# 真应该 404 的 URL
curl -sI "https://yourdomain.com/article/already-deleted" | head -1
# 期望:HTTP/2 404
# 真应该 200 的 URL
curl -sI "https://yourdomain.com/article/exists" | head -1
# 期望:HTTP/2 200
在 Search Console → 页面 → Soft 404 行 → “验证修复”。Google 7-21 天复审。
预防建议
- 任何”找不到资源”的代码路径都必须
res.status(404),不要默认 200 - 永久删除的内容用 410(更快从索引移除)
- 空的 category / tag / 搜索结果页要么 noindex 要么 404,不要返回 200 的空壳页
- SPA 部署必须区分”前端 404”和”服务端 404”,后者要由 SSR 函数返回真 404
- CI 加 smoke test:随机访问一个不存在 URL,确认返回 404 不是 200