无限滚动页内容未被收录:3 个原因 + 修复路径

首屏 20 条可见,下面 100 条 JS 加载——Google 看不见。

无限滚动列表页(首页、博客列表、商品分类)的 UX 很好,但对 SEO 有个致命问题:Googlebot 不滚动。它打开页面拿初始 HTML 就走,第 21 条之后用 JS 加载的内容它根本看不到。结果:

  • 列表上只有前 20 篇文章被发现
  • 第 21 篇之后即使存在也是 “URL unknown to Google”
  • 分页 URL(/blog?page=2/blog?page=3)如果没生成静态版本,也不会被爬

修法的核心:给无限滚动加一套 SEO 兜底的分页 URL

常见原因

1. 滚动加载靠 JS,Googlebot 不滚动

// React 典型实现:滚到底触发 fetch
useEffect(() => {
  const observer = new IntersectionObserver(([e]) => {
    if (e.isIntersecting) loadMore();
  });
  observer.observe(sentinelRef.current);
}, []);

Googlebot 不会跑这个 observer。它拿到的 HTML 里只有初始的 20 条 <article>

如何判断

curl -sL "https://yourdomain.com/blog" | grep -c "<article"
# 输出例如 20 = Google 只看到 20 个

2. 没有可爬的分页 URL

很多 SPA 的”加载更多”不更新 URL,永远停在 /blog。Google 没有任何方式访问”第 2 页”——因为 URL 不存在。

如何判断:滚到第 2 屏时看浏览器地址栏。如果还是 /blog,没分页 URL。

3. 内容首次出现在 client-only 渲染中

哪怕你做了 /blog?page=2,如果那个页面的内容仍然是 useEffect 后 fetch 的,Google 看到的还是空。要做 SSR / SSG。

4. 分页 URL 用 hash(#page=2

Hash 后面的 URL(fragment identifier)Google 不会爬。# 后内容对它而言是同一页。

5. 分页 URL 不在 sitemap 里

即使 /blog?page=2 是 server-rendered 的,如果 sitemap 没列出来、其他页面也没链过去,Google 不会发现。

最短修复路径

Step 1:每个无限滚动页都给”分页 fallback URL”

UX 保持无限滚动,但 URL 同时变化。当滚到加载第 2 页时:

// 用 History API 更新 URL 但不刷新
window.history.pushState({}, '', `/blog?page=2`);

并且 /blog?page=2 必须是一个真实可访问的 URL,独立 SSR / SSG。

Step 2:服务端渲染分页 URL

Next.js 示例:

// app/blog/page.jsx (App Router)
export default async function BlogPage({ searchParams }) {
  const page = parseInt(searchParams.page) || 1;
  const PER_PAGE = 20;
  const posts = await db.posts.findMany({
    skip: (page - 1) * PER_PAGE,
    take: PER_PAGE,
    orderBy: { publishedAt: 'desc' },
  });
  return (
    <>
      {posts.map(p => <article key={p.id}><a href={`/blog/${p.slug}`}>{p.title}</a></article>)}
      <nav>
        {page > 1 && <a href={`?page=${page - 1}`}>上一页</a>}
        <a href={`?page=${page + 1}`}>下一页</a>
      </nav>
    </>
  );
}

Astro 用 getStaticPaths 生成 /blog/page/1/blog/page/2 等静态文件。

Step 3:在 HTML 里加 rel=“next” / rel=“prev”(虽然 Google 2019 后不强调,但仍有用)

<link rel="prev" href="/blog?page=1" />
<link rel="next" href="/blog?page=3" />

或者更简单:底部有 <nav> 包含上下页和数字页链接(真 <a href>,不是 button + JS)。

Step 4:分页 URL 全部列进 sitemap

// scripts/generate-sitemap.mjs
const totalPosts = await db.posts.count();
const totalPages = Math.ceil(totalPosts / 20);
for (let i = 1; i <= totalPages; i++) {
  urls.push(`https://yourdomain.com/blog?page=${i}`);
}

部署后 Search Console 重新提交 sitemap,几天内 Google 会逐页爬。

Step 5:canonical 处理

每个分页 URL self-canonical,不要把 /blog?page=2 canonical 指向 /blog——那会让 Google 不收录 page 2 的内容。

<!-- on /blog?page=2 -->
<link rel="canonical" href="https://yourdomain.com/blog?page=2" />

Step 6:验证

# 静态 HTML 里应该看得到第 N 页的文章列表
curl -sL "https://yourdomain.com/blog?page=2" | grep -c "<article"
# 期望 20(或你的 page size)

在 Search Console URL Inspection 输入 /blog?page=2,“测试实际网址” → 看截图,列表是不是完整。

预防建议

  • 所有列表页设计 = 无限滚动 UX + 分页 URL SEO 兜底,两者并存
  • 内容生成时即写入 sitemap,自动覆盖新的分页 URL
  • 分页 URL 用查询参数(?page=2)或路径段(/page/2)都行,但要 server-rendered
  • 不要用 #page=2 这种 hash URL
  • 每周抽样 curl 一次分页 URL,确认 HTML 里能看到完整列表

相关阅读

标签: #SEO #收录 #排查