搜索全站
安装部署 13 分钟阅读

Headless WordPress 搭建方案:WPGraphQL 输出、前端选择与部署方式

WordPress 只当后台、前端用框架渲染,这套 Headless 架构到底怎么落地?本文对比 REST 与 WPGraphQL 两种取数方式,给出 Next.js 对接、内容变更触发重验证、以及 Nginx 分层部署的完整方案。

U
UFO_PF 2023-11-09 · 阅读 13 分钟
He 安装部署
阅读提示
本文约 5,051 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:想要 CDN 全站静态,又舍不得 WordPress 的后台

做内容站的痛点通常是两难的:纯静态站(Hugo/Jekyll/Astro)性能好但编辑体验差,客户不懂 Markdown;WordPress 后台好用,但每次请求都要跑 PHP 和 MySQL,流量高峰时 TTFB 直接飙到 800ms 以上。Headless 就是在这种张力下出现的折中——WordPress 继续当编辑后台和数据源,前端交给 Node 框架渲染成 HTML/CDN 静态文件。代价也很真实:预览功能要重做、SEO 元信息要自己接管、部署从”传个 FTP”变成两套系统。

原理分析:取数方式决定了整套架构

Headless 的第一步是决定前端怎么拿数据:

  • REST API:WordPress 内置,无需插件。优点是零依赖,缺点是字段冗余、多次往返(拿文章 + 作者 + 分类要三四个请求)。
  • WPGraphQL:安装同名插件后提供 /graph 端点,一次查询取完关联数据;配套的 WPGraphQL for WooCommerce 扩展覆盖商品、订单、变量商品。对复杂页面尤其划算。

第二步是渲染策略。Next.js 的 Incremental Static Regeneration(ISR,页面级 revalidate)允许页面像静态一样由 CDN 提供,同时按时间或按 webhook 重生。对博客、文档、导购页这种”写少读多”的场景几乎是标配。Astro/Nuxt 也提供了各自的 ISR/混合渲染能力。

第三步是变更联动:内容在 WordPress 里更新后,需要告诉前端”这篇要重生”。做法是在 WordPress 侧挂 save_post(或 wp_after_insert_post)打一个带密钥的 HTTP 请求到前端的 revalidate 接口。

组件 常见选择 说明
后端 WordPress + WPGraphQL 域名建议用 admin.xxx.com
前端 Next.js / Nuxt / Astro 按团队熟悉度选
数据 GraphQL 查询 支持 fragment 复用
触发 save_post → revalidate webhook 需共享密钥
部署 Vercel/Netlify + 独立 WP 主机 两端独立伸缩

保姆级实操

步骤 1:约定域名边界。 WordPress 后台域名(如 admin.example.com)与前端域名(如 www.example.com)分开。Hoisting 修改 WordPress 站点地址后,用 REST URL 过滤器把对外输出的地址指回前端:

// 后端站点地址设为 admin.example.com,对外 REST 地址仍由后端提供
add_filter( 'rest_url', function ( $url ) {
    return str_replace( 'https://admin.example.com', 'https://www.example.com', $url );
} );

步骤 2:安装 WPGraphQL 并验证。 插件启用后访问 /graph,用 GraphiQL IDE 试查询。注意它默认只暴露公开数据,admin.example.com/graphql 请勿对搜索引擎开放索引(GraphQL 端点本身也不该被索引)。

步骤 3:Next.js 侧用 App Router 取数据。 下面代码是真实可用的 App Router 写法:

const WP_GRAPHQL = process.env.WP_GRAPHQL_ENDPOINT;

async function gql(query, variables = {}, revalidate = 600) {
  const res = await fetch(WP_GRAPHQL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
    next: { revalidate }
  });
  if (!res.ok) throw new Error('GraphQL 请求失败:' + res.status);
  const json = await res.json();
  if (json.errors) throw new Error(json.errors[0].message);
  return json.data;
}

export async function generateMetadata({ params }) {
  const data = await gql(
    `query PostMeta($slug: ID!) {
       post(id: $slug, idType: SLUG) { title seo { metaDesc } }
     }`,
    { slug: params.slug }
  );
  return { title: data.post.title, description: data.post.seo?.metaDesc ?? '' };
}

export default async function Page({ params }) {
  const data = await gql(
    `query Post($slug: ID!) {
       post(id: $slug, idType: SLUG) { title content date author { node { name } } }
     }`,
    { slug: params.slug }
  );
  return (
    <article>
      <h1>{data.post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: data.post.content }} />
    </article>
  );
}

上例里 seo { metaDesc } 依赖 Yoast SEO 的 WPGraphQL 扩展(WPGraphQL Yoast SEO Addon),没装则要自己在后台用 register_graphql_fields 补充 meta 字段。

步骤 4:WordPress 内容更新触发前端重生:

add_action( 'wp_after_insert_post', function ( $post_id, $post ) {
    $trigger = [ 'publish', 'draft', 'trash' ];
    if ( ! in_array( $post->post_status, $trigger, true ) || wp_is_post_revision( $post_id ) ) {
        return;
    }
    $slug      = rawurlencode( $post->post_name );
    $secret    = get_option( 'mf_revalidate_secret' );
    $endpoint  = sprintf( 'https://www.example.com/api/revalidate?slug=%s&secret=%s', $slug, $secret );
    wp_remote_get( $endpoint, [ 'timeout' => 5, 'blocking' => false ] );
}, 10, 2 );

注意 blocking => false 很关键,否则编辑每次点发布都要等 HTTP 往返,后台明显卡顿。

步骤 5:前端侧实现重验证接口。 Next.js 里用 revalidatePath 按需刷新具体路径:

// app/api/revalidate/route.js
import { revalidatePath } from 'next/cache';
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  if (searchParams.get('secret') !== process.env.REVALIDATE_SECRET) {
    return Response.json({ message: 'invalid secret' }, { status: 401 });
  }
  const slug = searchParams.get('slug');
  if (!slug) return Response.json({ message: 'missing slug' }, { status: 400 });
  revalidatePath(`/blog/${slug}`);
  return Response.json({ revalidated: true, now: Date.now() });
}

步骤 6:Nginx 分层与预览解决。 后台域名禁止搜索引擎索引,前端域名不代理 /wp-admin(减少攻击面):

server {
    server_name www.example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;         # Node 前端
        proxy_set_header Host $host;
    }
    location /wp-content/uploads/ {
        proxy_pass https://admin.example.com;      # 图片等静态资源回源后端
    }
}

常见踩坑

  1. 后端与前端共用同一域名并用路径区分(/blog 走 Node,/wp-admin 走 PHP),Nginx location 优先级排错导致后台文件被 Node 接管。
  2. 案例: 某站 Headless 化后没做任何重验证触发,编辑更新文章三天后线上依旧是旧内容,被客户判定为”系统坏了”。
  3. dangerouslySetInnerHTML 直接输出 WordPress 正文,若后端被入侵则前端成为 XSS 放大器,应做 HTML 白名单清洗。
  4. 预览功能失效:草稿在 GraphQL 里默认查不到,需要 token 或专门开启 preview 插件。
  5. 忘了给 wp-json / graph 端点加速率限制,被脚本刷到 GraphQL 复杂查询拖垮 PHP。
  6. 图片 URL 仍是后台域名,前端域名下图片走跨域(多数情况可用,但 CDN 规则与证书要同步)。
  7. 后台主题从未升级,PHP 版本依旧 7.4,被安全扫描器标记为风险项。WordPress 性能优化与缓存策略

总结

判断要不要 Headless 的标准:内容更新频率低而读取量大、前端团队能维护 Node 服务、且有明确的性能诉求——三条满足两条才值得做;反之纯 WordPress + 全页缓存更划算。行动清单:前后端分域名 → 选 REST 或 GraphQL → 确定 ISR 策略 → 做好重验证钩子 → Nginx 分层 + 图片回源。切忌为了”技术先进”而把自己拖入两套运维。若仍想用 REST 而非 GraphQL,可以参考 WordPress REST API 输出优化。

U
UFO_PF作者

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

留下你的想法

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