综合实战:课程目录站(Headless 全流程)
这一课你将能做到
- 从零交付一个完整项目:WP 后台管理课程内容,Next.js(本站同款技术栈)前台展示,SSG+ISR 渲染
- 走完「需求 → 数据建模 → 后台就绪 → API → 前台 → 部署 → 运维」七步全流程
- 拿出一份可直接复用的上线检查清单,作为今后所有 WP 项目的交付模板
为什么要学
前十一课是零件,这一课是整车。面试讲项目、接单做交付、复盘查问题,都需要你能把主题、区块、插件、Hook、数据库、REST、安全、运维串成一条因果链。做完这个项目,这条路线图上每个节点的「为什么」你都亲身体验过。
人话:我们要做的就是一个迷你版 java-nav——课程数据由 WP 后台维护(编辑友好),前台用 Next.js 静态化(访问飞快)。这正是内容型产品的典型架构,做完即可写进简历。
先建立图像
WP 后台(course CPT + track 分类法 + level/duration 字段)
↓ REST /wp-json/qrocmq/v1/courses(聚合端点)
Next.js App Router
├─ /courses 列表页(ISR 300s)
├─ /courses/[slug] 详情页(SSG + on-demand revalidate)
↓
Vercel/Node 部署 → CDN
运维例行:每日 wp db export / 每周更新窗口 / Uptime 监控
跟着做
步骤 1:需求与数据建模
明确最小需求:课程有标题、简介、难度、时长、所属方向(track)、发布状态。建模决策:
- CPT
course(post_type),不是「往 post 里塞分类」——内容类型独立,模板与 API 才能独立 - 自定义分类法
track(如 Java/前端),归档与筛选走 taxonomy,天生有索引 level/duration用 postmeta(低频读写,不值得自定义表)
步骤 2:后台就绪(复用第 5 课骨架)
add_action( 'init', function () {
register_post_type( 'course', array(
'labels' => array( 'name' => '课程', 'singular_name' => '课程' ),
'public' => true,
'show_in_rest' => true, // REST 可用,Headless 必开
'has_archive' => false,
'rewrite' => array( 'slug' => 'course' ),
'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail' ),
'menu_icon' => 'dashicons-welcome-learn-more',
) );
register_taxonomy( 'track', 'course', array(
'labels' => array( 'name' => '方向' ),
'hierarchical' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'track' ),
) );
} );
用 WP-CLI 造演示数据:
wp post generate --post_type=course --count=20
wp eval '
foreach ( get_posts( array( "post_type" => "course", "numberposts" => 20 ) ) as $i => $p ) {
update_post_meta( $p->ID, "level", $i % 2 ? "advanced" : "beginner" );
update_post_meta( $p->ID, "duration", 8 + $i * 2 );
}'
步骤 3:聚合端点(第 9 课直接搬)
公开只读、带参数校验的 /qrocmq/v1/courses 与 /qrocmq/v1/courses/{slug}(代码见 wp-api 课步骤 2,把 meta_query 换成可选 level 过滤即可)。原则不变:permission_callback 必写、_fields 精简、不暴露草稿。
步骤 4:Next.js 前台
列表页与详情页骨架在第 9 课已完成,本步补齐两件事:
环境变量 .env.local:
WP_API_URL=https://cms.example.com
详情页按需失效(webhook 路由 app/api/revalidate/route.ts):
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST( req: NextRequest ) {
const secret = req.nextUrl.searchParams.get( 'secret' );
if ( secret !== process.env.REVALIDATE_SECRET ) {
return NextResponse.json( { ok: false }, { status: 401 } );
}
revalidateTag( 'courses' ); // WP 侧 save_post 后发 webhook 到这里
return NextResponse.json( { ok: true } );
}
WP 侧触发(第 5 课 hook 知识的实战):
add_action( 'save_post_course', function ( int $post_id, WP_Post $post ): void {
if ( 'publish' !== $post->post_status || wp_is_post_revision( $post_id ) ) {
return;
}
wp_remote_post( 'https://www.example.com/api/revalidate?secret=' . getenv( 'REVALIDATE_SECRET' ), array(
'timeout' => 5,
) );
}, 10, 2 );
步骤 5:安全与性能收尾(清单式套用)
- API 只读公开,写端点不存在 → nonce/能力检查范围极小(编辑后台自己有)
- WP 端:WP_DEBUG 关、DISALLOW_FILE_EDIT、登录限流、盐值就位(第 10 课清单)
- WP 端:Redis 对象缓存 + 整页缓存排除
/wp-json/动态部分(第 11 课) - 前端:_fields 精简、next/image 白名单 WP 域名、ISR 兜底 webhook
- 数据端:course 查询全走 WP_Query,无手写 SQL → prepare 问题天然不存在
步骤 6:部署
# WP:服务器 + HTTPS(或用托管面板)
wp core update && wp plugin update --all
# Next:Vercel 连 git 仓库,配置 WP_API_URL 与 REVALIDATE_SECRET
上线前演练:本地/预发环境完整走一遍「发文 → webhook → 前台 1 分钟内更新」;失败场景(webhook 超时)由 ISR 300s 兜底——任何链路都要有降级。
步骤 7:运维例行
- 每日:
wp db export计划任务 + 上传异地 - 每周:更新窗口(测试环境先验证)+
wp core verify-checksums - 监控:Uptime 拨测
/wp-json/qrocmq/v1/courses与前台首页;TTFB 曲线 - 回滚预案:代码回 git,数据回最近备份,10 分钟内可执行
讲清楚要点
- 建模先行:CPT/分类法/字段的决定一旦落地,前台与 API 全部跟着走——这一步多花一小时,后面少返工一周。
- 只读 API 是最简安全模型:前台不需要登录与写操作时,攻击面只剩 WP 后台本身,把后台藏好(HTTPS+限流+强口令)就覆盖了 80% 风险。
- 失效链路设计:SSG 不会自己变新,必须有「webhook 即时 + ISR 定时」双保险,且 webhook 要带 secret 鉴权。
- 全流程的因果串联:show_in_rest 决定 API 可用 → permission 决定安全 → 缓存分层决定时效 → 备份决定底线。任何一环缺失都有对应的课。
- 复用模板:本课的七步(需求/建模/后台/API/前台/部署/运维)可作为你所有内容型项目的 checklist。
你可能会懵的地方
-
用 ACF 还是手写 meta box? 面向客户交付用 ACF(省时间、界面成熟);学习与面试项目手写一遍(理解底层)。两者数据都存 postmeta,可迁移。
-
Headless 后 SEO 怎么保障? SSG/SSR 输出完整 HTML,搜索引擎正常抓取;再加 generateMetadata 输出标题/描述、结构化数据(JSON-LD)即可,与静态站无异。
-
图片放 WP 还是对象存储? 小站直接 WP uploads + CDN 域名;量大或多端消费再迁对象存储。REST 返回 URL,前端 next/image 接管优化。
-
多语言怎么做? 方案一 WPML/Polylang 在 WP 层翻译内容;方案二多语言作为不同 CPT 或 track 复用。Headless 下推荐 WP 层翻译、API 直出对应语言。
-
webhook 失败内容就不更新了? 有 ISR revalidate 兜底(最多迟到 TTL 时间);更稳的做法是 webhook 失败重试 + 管理端「手动触发失效」按钮。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| Next 直连 WP 数据库 | 耦合部署、绕过缓存与安全 | 只走 REST 端点 |
| webhook 无 secret | 任何人可触发失效/攻击 | 环境变量 secret 校验 |
| 端点一次拉全量无分页 | 数据增长后载荷爆炸 | 分页 + X-WP-Total + _fields |
| 上线不做预发演练 | 缓存/webhook 链路问题现场爆发 | 预发完整走一遍发布链路 |
| WP 与前台一起升级 | 出问题无法归因 | 分层发布,各自独立回滚 |
卡点急救
- Vercel 构建期拉不到 WP:WP_API_URL 在构建环境不可达(内网/防火墙),换成公网可达地址或预置静态兜底数据。
- 中文内容乱码:确认库表 utf8mb4(现代安装默认)、连接 charset 正确。
- 前台文章页 404:generateStaticParams 与 API slug 对不上(slug 与标题含特殊字符),核对 post_name 或显式指定 slug。
面试常问
-
完整介绍你的 Headless WP 项目架构与技术决策。 按「内容建模(CPT/分类/meta)→ API(自建命名空间+permission)→ 前端(SSG+ISR+webhook 失效)→ 缓存(Redis+页面缓存+CDN)→ 运维(备份/更新/监控)」五层叙述,每层给一个「为什么这样选」。
-
课程数据量涨到 10 万条,哪里先扛不住? ① 列表页 meta_query 筛选 → 迁自定义表加索引或冗余进 taxonomy;② API 全量分页 → 游标分页+缓存;③ 构建时长(SSG 全量)→ 改增量/按需渲染。给出瓶颈判断依据比背答案重要。
-
如何保证「发文后前台生效」这件事的可靠性? webhook(秒级,带 secret)+ ISR TTL(兜底)+ 手动失效入口(兜底的兜底),监控上对「发布→生效延迟」做拨测。
自测三问
- 为什么课程建模选 CPT + taxonomy + meta,而不是全部塞 postmeta?
- 「发文 → 前台更新」的链路里一共有几层缓存?各自的失效机制?
- 复述上线检查清单的七个步骤。
小结一句话
一个课程目录站 = WP 内容建模 + 只读 REST + Next.js SSG/ISR + 缓存分层 + 例行运维——十二课的知识在这条链路上各就各位,你就完成了从「学 WordPress」到「交付 WordPress」的闭环。
延伸
- Learn WordPress 官方课程
- WordPress Developer Resources
- 结课:回到 /roadmaps/wordpress 逐节点自检,标记掌握程度,查漏补缺。