7AA7 官网最早是一张手工写死的静态页。要上线博客、要能发文章、要能改文案,于是把 WordPress 塞了进来。最初的做法是「先跑起来」:路由自己写一套、设置自己存一份、权限自己判一遍。结果是站点长期维持着两套并行系统——任何一个需求都要改两个地方,越改越复杂。
这篇复盘记录我们把这套「外挂」逐步拆掉、让官网真正长回 WordPress 原生体系的过程。五个结构性坑,每个都写了根因和最终写法。
坑一:模板层级被 index.php 吃掉
症状:搜索页、分类页、标签页、日期归档、作者页,打开全都是首页。
根因是主题的 index.php 里只有一行:
<?php get_template_part( 'front-page' ); ?>
WordPress 的模板层级是「从具体回退到通用」的,index.php 是链条末端的兜底文件。把它指向首页,等于把整条回退链截断了——所有找不到专属模板的请求,都被静默地渲染成了首页。
这不仅是显示错误,还是 SEO 事故:/?s=任意关键词 会生成无穷多个「内容等于首页」的 URL,全是重复内容。
正确做法很朴素:把该有的层级文件补齐,让每一级各司其职。
front-page.php 只负责站点首页
archive.php 分类 / 标签 / 日期 / 作者 归档
search.php 搜索结果
index.php 最终兜底(绝不再指向首页)
补齐之后,归档页的标题、描述、分页、feed 全部由 WordPress 自己算出来,我们一行都不用写。
坑二:<head> 里三套输出管道打架
症状:<title> 出现两个、canonical 出现两个、og:url 声明的是会 301 跳转的地址。
根因是这个主题的架构——「静态快照 + 运行时改写」。模板先原样输出一份从旧静态站抓下来的完整 <head>,之后才调用 wp_head(),WordPress 核心又往里追加自己那一套。于是「只能出现一次」的标签必然重复;而快照里的 canonical / og:url 写的是非 www 域名,站点实际跑在 www 上——等于主动声明了一个「会跳转的规范地址」。
我们的处理原则是:静态快照只保留视觉与字体资源,语义标签一律交给 WordPress。核心是给 wp_head 挂一个最早的钩子,把不该出现的原生标签摘掉,再由主题统一输出一份:
add_action( 'wp_head', 'seven_slim_wp_head', 0 );
function seven_slim_wp_head() {
// 只保留一份实现,避免两处逻辑漂移导致某些页面标签重复
remove_action( 'wp_head', 'rel_canonical' );
// …统一输出 title / description / canonical / og / JSON-LD
}
重构后全站 12 个 URL 巡检,结果是每个页面 title=1 canonical≤1 robots≤1 非 www 域名=0。
坑三:自建表单保存 vs Settings API
症状:后台保存一次,其它子页的字段被清空。
这是我们的后台改造里最惊险的一次。原来自建的保存逻辑是「读 $_POST → 逐个 set_theme_mod()」。它看起来没问题,但它不在 WordPress 的 Settings API 体系里:没有 option group、没有 settings_fields()、没有 nonce 校验、也没有能力检查。
改成原生 options.php 之后,第一个真实保存就把 307 个字段写成了空字符串,/toolbox/ 直接 500。
根因在于 options.php 的语义:它遍历整个 option group,表单里没出现的字段会以 null 传下去;而 update_option() 的执行顺序是
sanitize_option() → get_option() → pre_update_option_{$option}
sanitize 跑在最前面,sanitize_text_field( null ) 得到空串——桥接层再也分不清「用户清空了它」和「这个字段不在当前页」。
修法两把锁:sanitize 回调遇到 null 直接返回 null;桥接层再检查一次
function seven_bridge_write_theme_mod( $value, $old_value, $option ) {
if ( null === $value ) { return $old_value; }
if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === strtoupper( (string) $_SERVER['REQUEST_METHOD'] )
&& ! array_key_exists( $option, $_POST ) ) {
return $old_value;
}
set_theme_mod( $option, $value );
return $old_value;
}
现在后台 5 个子页全部 POST 到原生 options.php,318 个设置项注册在 5 个 option group 里,wp_options 依然只有 3 行 7aa7_*——数据只有一份,保存在哪一页都不会动到别的页。
坑四:自建分类路由 vs category_base
症状:博客分类条上只有一项——WordPress 出厂的占位分类「Uncategorized」。
表面看是内容没归类,其实叠了两层问题。第一层是数据:所有文章都躺在默认分类里。第二层是架构——主题完全无视 WordPress 已经有的分类归档,自己写了一条重写规则:
add_rewrite_rule( '^blog/cat/([^/]+)/?$',
'index.php?pagename=blog&blog_cat=$matches[1]', 'top' );
自己维护路由的代价是:分页要自己写、feed 要自己写、REST 里没有它、后台「分类」列表点进去还是原生地址——两套地址同时存在。
其实 WordPress 早就支持给分类归档换前缀,只要在设置 → 固定链接里填两个选项:
category_base = blog/cat
tag_base = blog/tag
填完之后 /blog/cat/plugin-dev/ 就是一个货真价实的原生分类归档:查询、分页、feed、REST 全部自动可用;而旧的 /category/plugin-dev/ 会 404,不会留下重复内容。
模板侧也随之变得干净——链接用原生函数生成,高亮用原生条件判断,路径不再硬编码:
<a href="<?php echo esc_url( get_category_link( $c ) ); ?>">
<?php echo esc_html( $c->name ); ?>
</a>
顺带一提,WordPress 的归档标题默认带「分类:」「标签:」前缀。用原生过滤器去掉即可,不必自己拼字符串:
add_filter( 'get_the_archive_title', 'seven_blog_archive_title' );
坑五:自建用户体系 vs 原生 Capability
症状:博客的登录、注册、找回密码、权限判断,全是自己写的。
自建账号体系是二次开发里最容易上瘾、也最容易出事的一环:密码哈希、会话、找回密码令牌、越权判断——每一样都要自己兜住。而这些 WordPress 全都做好了,并且是经过十几年审计的。
我们把它拆了,换成原生:
- 登录 / 找回密码走
wp_signon()与retrieve_password(),前台只换皮肤; - 文章用原生
post,登录后直接进后台的富文本编辑器; - 权限用原生角色能力(
author/contributor),投稿是否需审核交给post_status; - 作者页用原生
display_name/description,作者链接用author_link过滤器品牌化。
有一处必须提醒:原生注册与找回密码都依赖发信。如果服务器出站 25 端口被封(不少云厂商默认如此),这两条路会静默失败——一定要在外网把 SMTP 的 465 / 587 调通再开放注册。
小结
回头看,这几次改造没有任何一次是「加功能」,全部是删掉自己写的那一套。判断标准也很简单:
如果一个需求,WordPress 的某个 API、某个过滤器、某个设置项已经能表达——那就不要自己再写一遍。二次开发应该只做两件事:换皮肤,以及决定哪些原生能力对用户可见。
技能点其实不多:模板层级、wp_head 钩子顺序、Settings API、分类法前缀、角色能力。把这五样吃透,剩下的工作量基本都在 CSS 上。

评论(0)
还没有评论,来抢沙发。
请 登录 后参与评论。