こんにちは。ダタク @iQeeeda です。
先日、WordPress で書きためた 156 記事を EmDash に移行しました。
移行ツールが本文も画像も運んでくれて、これで終わり。
...と思っていたら、そこから直すものが次々に出てきました。
結論: WordPress 移行で壊れるのは本文の外と、焼き付いた URL
先に結論です。
- 画像と内部リンクは絶対 URL で本文に焼き付いている。ドメインを変えると一斉にリンク切れ
- テーマの装飾クラスは
classだけが残り、CSS は移行先に来ない - 生 URL のリンク化は、除外リストに漏れたタグの属性値を巻き込む
- 「重複してるから
display: none」は、別の画面幅で穴になる - バルク修正スクリプトの無条件 publish は、非公開記事を復活させる
※ 全部このブログで実際に踏んだやつです。
今回は「WordPress から移行したあとに直すはめになったもの」を 5 つ紹介します。
移行先が自前のサイトで、CSS とスクリプトを自分で触れる人向けの話です。
※ 移行先の CMS は EmDash です。まだベータ版なので、挙動は今後どんどん変わると思います。2026 年 7 月時点のバージョンで踏んだ話として読んでください。ただし先に言っておくと、この 5 つはどれも EmDash の不具合ではありません。WordPress 側の作りと、僕が書いたスクリプトの問題です。
それでは解説していきます。
1. 画像と内部リンクが絶対 URL で焼き付いている
最初に踏んだのがこれです。
独自ドメインに切り替えた瞬間、全記事の画像が一斉に表示されなくなりました。
配信の設定は何も触っていないのに、記事から画像だけが抜け落ちます。
画像 src は相対パスではなく絶対 URL
本文に保存されている画像は、こういう形をしています。
https://[旧ホスト]/_emdash/api/media/file/01ABC....png 相対パスではなく、ホスト名込みの絶対 URL です。
移行した 156 記事を数えたら、1,279 箇所ありました。
ドメインを変えた瞬間に何が起きたか、まとめるとこうです。
- 画像: 1,279 箇所が旧ホストを指したまま残るので、全記事でリンク切れになる
- 内部リンク: 記事から記事への導線が全部 404
- パスキー: rpId が変わって管理画面に入れない ( 別記事に書きました )
対処: ホスト名を一括置換する
ここは本文を触るしかないので、諦めてスクリプトを書きました。
使ったのは scripts/fix-image-host.py です。
# 旧ホスト → 新ホストに一括置換する
python3 scripts/fix-image-host.py # dry-run (範囲確認)
python3 scripts/fix-image-host.py --apply # 本番に反映 移行先を決める時点でドメインも確定させておけば、この作業は要りません。
僕は仮ドメインで動かしてから独自ドメインに移したので、二度手間になりました。
ちなみにこのスクリプト、5 番で紹介するバグを持ったままです。あとで自白します。
2. テーマの装飾クラスは CSS が来ない
メリットボックスや囲みが、全部ただの箇条書きになっていました。
WordPress でこの手の装飾を作るとき、実体はテーマやプラグインが吐き出す HTML であることが多いようです。
本文に残るのは class の名前だけで、枠線を描いていた CSS はテーマ側にあります。
つまり移行先には、その class を解釈する CSS がどこにもありません。
直すのは本文ではなく移行先の CSS
クラス名はテーマによって違うので、僕はまず全記事を舐めて何が使われているかを数えました。
あとは移行先のレイアウトに CSS を足すだけです。
.article-content .merit,
.article-content .merit-box {
border: 1.5px solid var(--box-ok-border);
background: var(--box-ok-bg);
} 記事は 1 バイトも触らないので、失敗しても CSS を消せば元通りです。
当時の見た目を再現しようとせず、今のサイトのデザイントークンに寄せるのがオススメです。
3. 生 URL のリンク化処理が iframe の src を壊す
これは自分で壊しました。
移行後、本文の生 URL をクリックできるようにしたくて、リンク化するスクリプトを流しました。
その結果、旧 Amazon 広告の iframe が 13 記事ぶん、27 本とも表示されなくなりました。
除外リストに iframe が入っていなかった
スクリプトはこう書いていました。
URL_RE = re.compile(r"https?://[^\s\"'<>)\]]+")
# pre / code / script / style / a の「中身」は除外する
EXCLUDE_TAGS_RE = re.compile(r"<(pre|code|script|style|a)\b[^>]*>.*?</\1>", ...) 除外しているのは pre / code / script / style / a の 5 つだけです。
この正規表現は要素をまるごと掴むので、5 つのタグは属性値まで守られます。
一方 <iframe> はリストに無いので、src の中の URL が素通りでリンク化されます。
出来上がりがこれです。
<iframe src="<a href="https://rcm-fe.amazon-adsystem.com/...">https://...</a>"> src の値は 2 つ目の " で終わるので、ブラウザから見た src は <a href= です。
当然、何も表示されません。
- 除外をタグ名のホワイトリストにしない。リストに漏れたタグの属性値が丸腰になる
- やられたかどうかは
src="<aで grep すれば一発で分かる
同じ理由で <img> の src も 7 箇所巻き添えになっていました。
厄介なのは、やられても画面上は「広告が出てないな」で終わることです。目視では見つかりません。
4. display: none で消した目次が、モバイルで復活しない
旧記事には WordPress 時代の目次が埋まっています。
今のサイトはサイドバーに目次を出しているので、重複するだけです。
なので display: none で消しました。
サイドバー自体が 1100px で消えていた
ここに穴がありました。
うちのサイドバーは、画面幅 1100px 以下で display: none になります。
つまりスマホで開くと、旧目次を潰した 121 記事には目次が 1 つも無い。
この記事を書いている最中に気づきました。今まさに直しているところです...
対処: 消す前に「残す側が消える条件」を見る
DevTools で幅を 375px まで縮めて、消した側と残した側が同時に消えないかを確認する。
それだけで済む話でした。
「重複してるから片方を消す」は、重複が成立する条件つきの判断です。消す側ではなく、残す側がどの条件で消えるかを先に確認してください。
5. バルクスクリプトの publish で非公開記事が復活する
最後は一番実害が出たやつです。
移行後、僕は画像を貼り直すスクリプトを全記事に流しました。
このスクリプトが、変更した記事を無条件で publish していました。
結果、非公開にしていた 5 記事と空の下書きが 1 本、勝手に公開されました。
publish は status を見てから叩く
直したコードがこれです。
# 公開中のものだけ publish する。draft / 非公開化済みの記事を
# 一括処理のついでに公開してしまわないためのガード
if it.get("status") == "published":
api("POST", f"/_emdash/api/content/{collection}/{it['id']}/publish", {}) たった 1 行の if です。
バルク処理は「本文をどう書き換えるか」ばかり気にしていて、公開状態も一緒に書き換わることを忘れていました。
白状すると fix-image-host.py はまだ直っていない
1 番で紹介した fix-image-host.py、あれは今も無条件で publish しています。
この記事を書きながら気づきました。
事故を起こして、再発防止のルールまで書いた本人が、隣のスクリプトに同じ地雷を残していたわけです。
知っていても踏むので、バルク処理を書くたびに status を見る癖をつけるしかないなと。
移行の見積もりで数えるべきだったのは、記事数ではなく URL の本数とスクリプトの行数でした。
移し終わってからが本番です。
まだコメントはありません