Soft 404——页面在 Google 眼里像空的

Search Console Soft 404 不是内容质量问题,而是工程问题:服务端该返回 404 却给了 200。本文从空 tag 页、空搜索结果、SPA 路由三个常见来源给出修复路径。

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

相关阅读

标签: #SEO #Google #Search Console #收录