本文约 6,215 字,预计阅读 16 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:换完主题,一半内容「消失」了
换主题上线当天最常见的三类反馈:文章里出现一堆方括号如 [row][col size="6"],产品介绍页的价格、参数全部空白,产品详情页打开是 404。站长的第一反应是「新主题有 bug」,实际上内容一条没丢——它们还在数据库里,只是新主题不知道怎么读、怎么渲染。
原因在 WordPress 的内容模型上:正文是一整段 HTML 文本存进 wp_posts.post_content,自定义字段是以键值对形式存在 wp_postmeta,文章类型由主题的 register_post_type() 声明,缩略图尺寸由 add_image_size() 注册。前两类跟着内容走,后两类跟着主题走。换主题时后两类消失了,前两类因为缺少渲染逻辑而显示异常,于是看起来像「内容丢了」。
所以换主题的正确流程不是「装上新主题 → 启用 → 看看哪里不对 → 逐个修」,而是「先盘点 → 先把不跟着走的资产平移到新主题 → 在预发布环境切 → 逐项验证 → 上线」。
原理分析:哪些资产跟着内容走,哪些跟着主题走
| 资产 | 存储位置 | 换主题后是否保留 |
|---|---|---|
| 正文 HTML | wp_posts.post_content |
保留,但短代码可能失效 |
| 自定义字段 | wp_postmeta |
保留,但读取代码失效 |
| 媒体文件 | wp_posts + uploads |
保留,尺寸可能缺失 |
| 菜单 | wp_terms / wp_term_relationships |
保留,菜单位置(theme_location)失效 |
| 小工具 | wp_options |
保留,但主题侧边栏区域失效 |
| 文章类型 CPT | 主题/插件 register_post_type() |
主题声明的会消失 → 404 |
| 缩略图尺寸 | add_image_size() |
未注册的只剩原图尺寸 |
| 短代码 | 主题/插件 add_shortcode() |
主题定义的会变成明文 |
一句话总结:凡是「存在数据库里」的都不会丢,凡是「由主题代码声明」的都会丢。迁移工作的全部内容,就是把后一类在新主题里重新声明一遍,或者移到插件里让它彻底脱离主题。
保姆级实操
步骤 1:换之前先盘点,把所有依赖列出来
-- 统计正文里用到的所有短代码及其出现次数
SELECT
REGEXP_SUBSTR(post_content, '\\[[a-zA-Z0-9_-]+') AS shortcode,
COUNT(*) AS cnt
FROM wp_posts
WHERE post_status = 'publish'
AND post_content LIKE '%[%'
GROUP BY shortcode
ORDER BY cnt DESC
LIMIT 40;
MySQL 8.0 才支持 REGEXP_SUBSTR,5.7 环境改用下面这条更朴素的方式逐个确认:
-- 逐个确认某个短代码被多少篇文章使用
SELECT ID, post_title
FROM wp_posts
WHERE post_status = 'publish'
AND post_content LIKE '%[row]%';
同时盘点自定义字段:
SELECT meta_key, COUNT(*) AS cnt
FROM wp_postmeta
GROUP BY meta_key
ORDER BY cnt DESC
LIMIT 50;
以及注册了哪些文章类型:
wp post-type list --fields=name,label,public,_builtin --format=table
步骤 2:把 CPT 与分类法从主题挪到插件(最关键的一步)
这是唯一能一劳永逸解决问题的办法,因为 CPT 本来就不属于主题层:
<?php
/**
* Plugin Name: 站点内容类型(产品 / 案例)
* Description: 脱离主题的产品与案例 CPT 声明,换主题不受影响
* Version: 1.0.0
*/
add_action( 'init', 'mmf_register_content_types' );
function mmf_register_content_types() {
register_post_type( 'product', array(
'labels' => array(
'name' => '产品',
'singular_name' => '产品',
),
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'product' ),
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'show_in_rest' => true,
'menu_icon' => 'dashicons-cart',
) );
register_taxonomy( 'product_cat', 'product', array(
'labels' => array( 'name' => '产品分类' ),
'public' => true,
'hierarchical' => true,
'rewrite' => array( 'slug' => 'product_cat' ),
'show_in_rest' => true,
) );
}
register_activation_hook( __FILE__, function() {
mmf_register_content_types();
flush_rewrite_rules();
} );
装好这个插件后,先在旧主题下启用一次并访问前台确认 URL 不变,再切主题,就能彻底避免 CPT 404。
步骤 3:给失效短代码做兼容层,而不是改内容
内容迁移的最后手段才是改正文。正确做法是在插件或新主题里注册同名短代码,把旧语法翻译成新主题的标记:
<?php
// 旧主题的 [row] [col size="6"],翻译成新主题的栅格 HTML
add_shortcode( 'row', function( $atts, $content = null ) {
return '<div class="mmf-grid">' . do_shortcode( $content ) . '</div>';
} );
add_shortcode( 'col', function( $atts, $content = null ) {
$atts = shortcode_atts( array( 'size' => '12' ), $atts, 'col' );
$size = absint( $atts['size'] );
return '<div class="mmf-grid__col mmf-grid__col--' . $size . '">'
. do_shortcode( $content ) . '</div>';
} );
// 旧主题的 [button url="" text=""],新主题没有按钮短代码
add_shortcode( 'button', function( $atts ) {
$atts = shortcode_atts( array(
'url' => '',
'text' => '查看详情',
), $atts, 'button' );
return sprintf(
'<a class="wp-block-button__link" href="%s">%s</a>',
esc_url( $atts['url'] ),
esc_html( $atts['text'] )
);
} );
这样既不用改几十篇文章,又保留了内容的可移植性。等确认新主题运行稳定后,再考虑是否做一次性清理(参见短代码批量清理的做法)。
步骤 4:把自定义字段的读取逻辑也挪进插件
内容保留但读不出来,等价于丢失。写一个统一的取值函数:
<?php
function mmf_get_product_spec( $post_id = null ) {
$post_id = $post_id ?: get_the_ID();
$raw = get_post_meta( $post_id, '_product_spec', true );
if ( '' === $raw ) {
// 兼容旧主题的字段名
$raw = get_post_meta( $post_id, 'product_spec', true );
}
if ( ! is_array( $raw ) ) {
$raw = array_filter( (array) $raw, 'strlen' );
}
return array_map( 'sanitize_text_field', (array) $raw );
}
模板里统一调用 mmf_get_product_spec(),换主题时只要这个函数还在,数据就不会掉。字段名兼容的逻辑写在这里,将来清理旧字段时改一处即可。
步骤 5:补齐缩略图尺寸并重建
<?php
add_action( 'after_setup_theme', 'mmf_image_sizes' );
function mmf_image_sizes() {
add_image_size( 'product-card', 480, 360, true );
add_image_size( 'case-cover', 800, 500, true );
}
新尺寸只对之后上传的图生效,历史图片需要重建缩略图:
# 先干跑确认影响范围
wp media regenerate --dry-run
# 只重建缺的尺寸,跳过已存在的
wp media regenerate --only-missing --yes
--only-missing 很重要,全量重建大图库可能跑几个小时并且占用大量 CPU。
步骤 6:菜单与小工具的平移
菜单本身保留,但「显示位置」绑定的是旧主题注册的 theme_location。新主题用 register_nav_menus() 声明自己的位置,需要在「外观 → 菜单」里重新指派一次。小工具从 WordPress 5.8 起是区块,旧的文本小工具内容会保留在「小工具」编辑器的经典区块里,需要进「外观 → 小工具」检查,或直接在站点编辑器里重建。
步骤 7:全站验证
# 对比新旧主题的 URL 清单,确认没有 URL 变化
wp post list --post_type=any --post_status=publish \
--field=url --posts_per_page=-1 > /tmp/urls_new.txt
diff /tmp/urls_old.txt /tmp/urls_new.txt && echo "URL 一致"
再做一次全站抓取(Screaming Frog 或 wget --spider)确认无 404,最后用 wp search-replace 检查是否有残留的旧主题路径:
wp search-replace 'wp-content/themes/old-theme' 'wp-content/themes/new-theme' --dry-run
常见踩坑
- 直接在生产环境切主题:应该在预发布环境完整走一遍,尤其是带 WooCommerce 的站点。
- CPT 的
rewrite slug改了:即使 CPT 保住了,slug 变化会让所有产品 URL 变化,直接损失排名。slug 必须与旧主题完全一致。 - 忘记
flush_rewrite_rules():装完 CPT 插件后前台仍 404,去「设置 → 固定链接」点一次保存即可。 - 短代码兼容层里忘记
do_shortcode( $content ):嵌套的[row][col]只有外层被解析,内层变明文。 - 案例: 某站点换主题时把产品 CPT 留在了旧主题代码里,新主题启用后产品页全部 404,运维以为是缓存问题,清了三天缓存。期间 Google 把这些 URL 判定为软 404,两个月后排名才恢复。教训:CPT 必须在切主题之前就抽到插件里,并在旧主题下先验证一次 URL 不变。
- 重建缩略图时没开
--only-missing:两万张图全量重建跑了六小时,期间服务器负载打满,前台超时。 - 没备份
wp_options里的主题设置:切主题后旧设置还在库里但读不到,想回退时旧主题的设置已被新主题覆盖。切主题前务必wp db export完整备份一次。
总结
换主题的内容保全,本质上是把「跟着主题走的东西」提前搬到插件层:CPT 与分类法、自定义字段读取函数、短代码兼容层、图片尺寸。这四项搬完,换主题就只剩视觉适配。
执行顺序建议固定为:盘点(短代码 / 字段 / CPT 三张清单)→ 抽插件并在旧主题下验证 URL 不变 → 预发布环境切换并逐项验证 → 全站抓取查 404 → 上线。每一步都要有 wp db export 备份兜底。
如果评估下来发现要搬的东西太多,先回到 主题二次开发的成本评估 确认换主题是否真的划算;迁移完成后记得按 主题切换对SEO的短期影响与应对 检查 URL 与结构化数据的变化。
