搜索全站
Google SEO 12 分钟阅读

Headless WordPress 的 SEO 方案:预渲染与动态渲染怎么选

前台换成 Next.js 后谷歌抓不到正文?本文讲清 SSR、SSG、ISR 与动态渲染的差别,给出 Headless WordPress 的预渲染落地步骤与验证方法,含代码。

U
UFO_PF 2021-01-16 · 阅读 12 分钟
He GOOGLE SEO
阅读提示
本文约 4,762 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:前台很丝滑,谷歌却只看到一堆 script

做外贸站和品牌站的朋友这两年大量转向 Headless 架构:WordPress 只当后台,前台用 Next.js、Nuxt 或 Astro 渲染。上线后页面体验确实快,但一个月后打开 Google Search Console 的”网页”报告,发现大量 URL 显示”已抓取,但目前未编入索引”,或者索引了但搜索结果里摘录的是空白。用 URL 检查工具的”测试实际网页”一看,渲染后的 HTML 里 <main> 区域是空的,正文全靠客户端 fetch 拉回来。

问题不在 WordPress,而在渲染时机。传统 WordPress 主题是服务端把完整 HTML 吐给浏览器,Googlebot 拿到就能解析;Headless 站点如果走纯客户端渲染(CSR),Googlebot 第一次拿到的是骨架 HTML,必须依赖 Web Rendering Service(WRS)排队执行 JavaScript 才能看到内容,这个队列可能延迟数小时到数天,赶上抓取预算紧张就永远排不上。所以 Headless 站的 SEO 核心命题只有一个:让爬虫第一次请求就拿到完整 HTML。

原理分析:五种渲染方式的收录差异

Google 的索引流程分两阶段:先抓取原始 HTML 并抽取链接、结构化数据,再把页面放进渲染队列执行 JS 补全 DOM。第一阶段拿不到的主内容,就只能赌第二阶段的队列资源。

方式 首次 HTML 收录稳定性 适用场景
CSR 纯客户端 骨架 差 后台、登录后页面
SSR 服务端渲染 完整 好 内容频繁变化
SSG 静态生成 完整 最好 文章、产品页
ISR 增量静态再生成 完整 最好 大体量内容站
动态渲染(按 UA 分流) 爬虫给完整 一般 老系统临时过渡

Google 官方文档明确把动态渲染定位为”workaround(临时替代方案)”,不是长期推荐方案,原因有二:一是给爬虫和用户看到不同内容,容易被判定为伪装(cloaking)风险;二是维护两套渲染链路成本高。能预渲染就预渲染,动态渲染只留给无法改造的老项目。

预渲染也不是免费午餐:ISR 需要设计好”谁去触发重新生成”,否则编辑在后台改完文章,前台还是旧内容,反而制造了另一类 SEO 问题——页面内容与结构化数据不一致。

保姆级实操

步骤 1:确认内容出口。 WordPress 后台装 WPGraphQL 插件,把内容以 GraphQL 暴露;若不想装插件,也可直接用 REST API 的 /wp-json/wp/v2/posts?_fields=id,slug,title,excerpt,content&per_page=100。REST 更通用,GraphQL 取字段更省流量。

步骤 2:文章页用 ISR 生成静态 HTML。 用 generateStaticParams 预生成热门 slug,未预生成的走按需生成并缓存。

// app/blog/[slug]/page.jsx —— Next.js App Router + ISR
const ENDPOINT = 'https://wp.example.com/graphql';

async function fetchPost(slug) {
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      query: `query($slug: ID!) {
        post(id: $slug, idType: SLUG) {
          title
          content
          modified
          seo { title desc canonicalJson schema }  # 由 WPGraphQL for SEO 扩展提供
        }
      }`,
      variables: { slug },
    }),
    next: { revalidate: 3600 }, // 每小时后台再生成一次
  });
  const { data } = await res.json();
  return data?.post ?? null;
}

export async function generateMetadata({ params }) {
  const post = await fetchPost(params.slug);
  return { title: post?.seo?.title, description: post?.seo?.desc,
           alternates: { canonical: post?.seo?.canonicalJson?.canonical } };
}

export default async function Page({ params }) {
  const post = await fetchPost(params.slug);
  if (!post) notFound();
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      <script type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(post.seo.schema) }} />
    </article>
  );
}

步骤 3:让后台编辑触发重新生成。 用 transition_post_status 钩子在文章发布或更新时回调前台的 revalidate 接口,避免编辑等一小时。

<?php
// 主题 functions.php 或自定义插件
add_action('transition_post_status', function ($new, $old, $post) {
    if ($post->post_type !== 'post') return;
    if ($new !== 'publish' && $old !== 'publish') return;

    $slug = $post->post_name;
    wp_remote_post('https://front.example.com/api/revalidate', [
        'blocking' => false,          // 不阻塞后台保存动作
        'headers'  => ['Content-Type' => 'application/json'],
        'body'     => wp_json_encode([
            'path'  => '/blog/' . $slug,
            'token' => getenv('REVALIDATE_TOKEN'), // 从环境变量读取,禁止硬编码
        ]),
    ]);
}, 10, 3);

前台接口需校验 REVALIDATE_TOKEN,再调用 revalidatePath(path),切勿开放给匿名请求。

步骤 4:用 Googlebot UA 自检原始 HTML。 不要只看浏览器渲染结果,要看服务端吐出的源码。

# 对比普通 UA 与 Googlebot UA 拿到的原始 HTML 字节数
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
  https://front.example.com/blog/hello | grep -c "<h1"

# 直接查看服务端返回的正文长度,判断是否为骨架页
curl -s https://front.example.com/blog/hello | wc -c

步骤 5:提交并验证。 用 next-sitemap 生成 sitemap.xml,提交到 Search Console;再用”网址检查 → 测试实际网址 → 查看已抓取的网页”确认渲染后 HTML 含正文,并用富媒体搜索结果测试工具校验 JSON-LD。这一步通过,才算真的解决。

关于整体收录策略的补充,可配合 Google Search Console 使用教程 中的索引报告一起看。

常见踩坑

  1. 案例: 某外贸站把 getStaticProps 写成 useEffect 里 fetch,上线两个月 3000 篇文章只收录 47 篇。改成 SSG 后,两周内收录率回到 92%。判断方法很简单:禁用浏览器 JS 后刷新页面,看不到正文就是 CSR。
  2. 只给 Googlebot 返回预渲染 HTML、给真人返回 CSR 页面,若两边内容差异过大(比如爬虫版塞了额外关键词),会被判定为伪装站点,风险远高于收益。
  3. ISR 的 revalidate 时间设成 7 天,导致价格与库存长期不更新,结构化数据里的 offers.price 与实际不符,购物类富媒体结果被人工处置移除。
  4. Headless 后仍在 WordPress 端装 Yoast 生成 sitemap,而前台域名完全不同,提交的 sitemap 里全是后台域名,等于没提交。sitemap 必须生成在前台域名下。
  5. 忘记处理 robots.txt 与 canonical:前台、后台两个域名同时可访问,形成重复内容。后台域名应加 noindex 响应头或全站 301 到前台。
  6. 分页与分类页没有对应的静态路由,/blog/page/2 是纯客户端路由,Google 抓到 200 但正文为空,形成大量软 404。
  7. 过度依赖渲染队列:Google 的渲染资源有限,页面 JS 包超过 1MB 时经常渲染超时,主内容再次丢失。

总结

判断标准只有一条:关闭 JavaScript 后能否看到完整正文。能看到,架构就是安全的;看不到,就必须改造成 SSR 或 SSG/ISR。实施顺序建议为——先把文章与产品页做成 ISR,再补 sitemap 与 canonical,最后用 GSC”测试实际网址”逐条验证。动态渲染只作为老系统的半年过渡方案,且必须保证爬虫版与用户版内容实质一致。 Headless 不是 SEO 的敌人,把内容提前渲染出来才是它的正确打开方式,相关前端性能取舍可参考 Core Web Vitals 优化指南。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。