REST API 与 Headless(WP + Next.js)
这一课你将能做到
- 消费 WP 内置 REST API(列表、详情、分页、_fields/_embed),并说清三种认证方式
- 用 register_rest_route 自建带 permission_callback 的自定义端点
- 用本站同款 Next.js App Router 把 WP 当 Headless CMS:SSG 静态化 + ISR 增量更新
为什么要学
传统 WP 的前台由 PHP 主题渲染;Headless 模式把前台换成 Next.js——WP 只当内容仓库和编辑后台。这正是本站 java-nav 的架构思路:编辑在后台改内容,前端用 SSG/ISR 渲染,CDN 直接回静态页,性能与安全面同时受益。
人话:把 WP 想成一个自带后台的内容数据库,/wp-json/ 就是它的查询窗口。Next.js 在构建期把窗口里的数据「烤」进静态页面,编辑一发文,ISR 到点自动重烤——内容与展示彻底解耦。
先建立图像
编辑者 → WP 后台(文章/课程CPT) → MySQL
↓
REST /wp-json/wp/v2/... ← 数据出口
↓ 服务端 fetch
Next.js (app router)
build 时: generateStaticParams → 烤出全部课程详情页(SSG)
运行时: revalidate=300 → ISR 每 5 分钟后台重验证
↓
Vercel/Node 服务 → CDN → 用户浏览器
对比传统 WP:请求每次都打到 PHP+MySQL;Headless 下绝大多数用户请求根本不碰 WP。
跟着做
步骤 1:消费内置端点
# 列表:只取需要的字段,减小载荷
curl -s "http://localhost:8080/wp-json/wp/v2/posts?per_page=3&_fields=id,title,slug,link"
# 分页信息在响应头:X-WP-Total(总数) / X-WP-TotalPages
curl -s -I "http://localhost:8080/wp-json/wp/v2/posts" | grep -i x-wp
# 详情按 slug 取
curl -s "http://localhost:8080/wp-json/wp/v2/posts?slug=hello-world&_fields=title,content"
_fields 精简返回、_embed 一次带出作者/特色图等关联——这两个参数决定前端体验。
步骤 2:自建一个聚合端点
插件中注册:
add_action( 'rest_api_init', function () {
register_rest_route( 'qrocmq/v1', '/courses', array(
'methods' => WP_REST_Server::READABLE,
'permission_callback' => '__return_true', // 公开只读;写操作必须校验能力
'args' => array(
'level' => array(
'type' => 'string',
'enum' => array( 'beginner', 'advanced' ),
'sanitize_callback' => 'sanitize_key',
),
),
'callback' => function ( WP_REST_Request $request ) {
$query = new WP_Query( array(
'post_type' => 'course',
'post_status' => 'publish',
'posts_per_page' => 20,
'meta_query' => array(
array(
'key' => 'level',
'value' => $request->get_param( 'level' ) ?? '',
'compare' => '!=',
),
),
) );
$items = array_map(
fn( WP_Post $p ): array => array(
'id' => $p->ID,
'title' => get_the_title( $p ),
'slug' => $p->post_name,
'level' => get_post_meta( $p->ID, 'level', true ),
),
$query->posts
);
return rest_ensure_response( $items );
},
) );
} );
验证:curl -s "http://localhost:8080/wp-json/qrocmq/v1/courses?level=advanced"。命名空间 qrocmq/v1 与核心的 wp/v2 隔离。
步骤 3:认证方式三选一
# 1) Application Passwords(推荐,6.x 内置):后台 用户→个人资料→应用密码 生成
curl -s -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
"http://localhost:8080/wp-json/wp/v2/users/me"
# 2) 浏览器内已登录:Cookie + wp_rest nonce(同域前端)
// 前端: fetch(url, { credentials: 'include', headers: { 'X-WP-Nonce': QROCMQ_CFG.nonce } })
# 3) JWT 插件:适合跨域移动端/微服务,需配 CORS
原则:服务端到服务端用 Application Passwords;浏览器同域交互用 Cookie+nonce;跨系统用 JWT 并收紧白名单。
步骤 4:Next.js 前台(本站同款写法)
app/courses/page.tsx(列表):
type Course = { id: number; title: string; slug: string; level: string };
export const revalidate = 300; // ISR:5 分钟增量再生
async function getCourses(): Promise<Course[]> {
const res = await fetch(
`${process.env.WP_API_URL}/wp-json/qrocmq/v1/courses`,
{ next: { revalidate: 300, tags: ['courses'] } }
);
if ( ! res.ok ) return [];
return res.json();
}
export default async function CoursesPage() {
const courses = await getCourses();
return (
<ul>
{ courses.map( ( c ) => (
<li key={ c.id }>
<a href={ `/courses/${ c.slug }` }>{ c.title } · { c.level }</a>
</li>
) ) }
</ul>
);
}
app/courses/[slug]/page.tsx(详情,SSG):
export async function generateStaticParams() {
const res = await fetch( `${process.env.WP_API_URL}/wp-json/qrocmq/v1/courses` );
const courses = await res.json();
return courses.map( ( c: { slug: string } ) => ( { slug: c.slug } ) );
}
export async function generateMetadata( { params }: { params: Promise<{ slug: string }> } ) {
const { slug } = await params;
const course = await getCourse( slug ); // 复用 fetch 逻辑
return { title: course?.title ?? '课程详情' };
}
步骤 5:让「发文 → 前端更新」联动
两条路任选或叠加:ISR 到点自动重验证(上面的 revalidate);编辑发布时 WP 侧发 webhook,Next.js 收到后调用 revalidateTag( 'courses' )(on-demand revalidation),实现秒级生效。
讲清楚要点
- permission_callback 是安全底线:从 WP 5.x 起省略它即默认公开;写操作必须校验
current_user_can(),公开端点只读。 - 参数三件套:type/enum 约束、sanitize_callback 清洗、default 兜底——REST 层的输入验证在这里完成。
- Headless 的收益与代价:收益是前端自由(React 生态)、性能可控(CDN 静态)、攻击面缩小(只暴露只读 API);代价是失去主题生态、编辑预览与表单要自建、运维两条线。
- 缓存分层决定一致性:WP 对象缓存/页面缓存 + Next 数据缓存/全路由缓存 + CDN,任何一层都可能让「发文不生效」——排查顺序从 CDN 往里剥。
- 本站选型逻辑:java-nav 内容更新频率低、SEO 要求高 → SSG 打底 + ISR 增量是性价比最高的组合,无需为实时性上 SSR。
你可能会懵的地方
-
跨域调用 REST 被 CORS 拦? 浏览器直连才有 CORS 问题。本站做法是 Next.js 服务端 fetch(Node 到 Node 无 CORS);确需浏览器直连,才配置
rest_pre_serve_request过滤器或代理。 -
nonce 什么时候需要? 只在「浏览器内已登录用户」发起写请求时用(防 CSRF)。匿名公开读、服务端用 Application Passwords 的场景,都不需要 nonce。
-
X-WP-Total 为什么在 body 里拿不到? 分页统计放在响应头。前端要总数就先
HEAD或读 header;Next.js 里res.headers.get('x-wp-total')。 -
发文了 Next 页面没更新? 按层排查:WP 端缓存(WP-CLI 清)、Next 数据缓存(revalidate/webhook)、CDN 缓存(清缓存或降 TTL)。确认 WP API 直出已是新数据,再往前找。
-
媒体文件怎么处理? 图片仍存 WP uploads,REST 返回
wp:featuredmedia(配合 _embed)。Next 用 next/image 加remotePatterns白名单 WP 域名,享受压缩与懒加载。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| register_rest_route 不写 permission_callback | 默认公开,写接口等于裸奔 | 读可公开、写必校验能力 |
| 前端组件里存 WP 管理员账密 | 密钥泄露 | 服务端环境变量 + Application Passwords |
| 在浏览器直连 WP 拉全量字段 | 载荷大 + CORS | 服务端 fetch + _fields 精简 |
| 所有页面都用 SSR 实时打 WP | WP 成瓶颈 | SSG+ISR,写操作才考虑 SSR |
| 端点一次返回全站数据 | 载荷爆炸 | 分页 + X-WP-Total + 按需字段 |
卡点急救
- 404 自定义命名空间:路由没生效时重访一次「设置→固定链接」(刷新 rewrite 规则)即可。
- 401 rest_forbidden:permission_callback 返回了 false——核对你的能力检查与登录态;跨域时检查 cookie 是否带上。
- Next 构建期 fetch 失败:构建环境访问不到 WP(内网/自签证书),把 WP_API_URL 配成可达地址或改为运行时获取。
面试常问
-
设计一个 Headless WP + Next.js 架构,你会怎么选渲染策略? 内容型页面 SSG 打底(generateStaticParams)+ ISR(revalidate 或 webhook 按需重验证);用户相关内容客户端请求只读端点;搜索等强实时页面可 SSR。理由:SEO、性能、WP 负载三者平衡。
-
REST API 的三种认证方式和适用场景? Cookie+wp_rest nonce(浏览器内已登录用户,防 CSRF);Application Passwords + Basic Auth(服务端间调用,可吊销);JWT(跨域/移动端,需自管过期与 CORS)。
-
发文后前端多久生效?怎么做到秒级? 取决于缓存层:默认 ISR 到点重验证;接入 WP webhook → Next revalidateTag 即按需失效,秒级生效;CDN 层再按路径定向清除。
自测三问
- register_rest_route 的哪个参数决定端点是否公开?写端点还必须做什么?
- _fields 和 _embed 分别解决什么问题?
- SSG、ISR、SSR 三者的取舍标准是什么?本站选哪种,为什么?
小结一句话
WP 在 Headless 架构里只做「内容仓库 + 编辑后台」,REST 是唯一出口;Next.js 用 SSG+ISR 把出口数据烤成 CDN 可缓存的页面——这就是本站同款的前后端解耦方案。
延伸
- WP REST API 官方手册
- Next.js 数据获取与缓存
- 下一课:安全加固——nonce、转义、防注入与一次完整的生产加固。