この記事の結論
WordPressを静的化してlocalhostのリンクが残るのは、置換をポート1つ(僕の会社では8990)に決め打ちしたときです。導入時の8977が残って全ページのナビが壊れました。公開前チェックの形も載せます。
目次6項目
WordPressを静的化してlocalhostのリンクが残ったとき、僕の会社の原因は、置換を「そのとき使っていたポート」1つに決め打ちしていたことでした。2026年8月には、置換から漏れた古いポートのURLが、全ページのグローバルナビに残ったまま公開されています。
残る理由と、ポートを問わない置換、公開前に止める検査を順に書きました。「手元では動いたのに、公開したらリンクがおかしい」という方は、次の章の表から見てください。
WordPressを静的化してlocalhostのリンクが残る原因:置換するURLが1つだけだった
localhostのリンクが残る多くの原因は、データベースの中に、いま使っていない古いURLが絶対URLで残っているためです。置換や検査をいまのURLだけで行うと、それをすり抜けます。
ここでいう静的化とは、WordPressが表示のたびに組み立てるページを、あらかじめHTMLファイルとして書き出す作業を指します。書き出したファイルは、WordPressの無いサーバーにも置けます。
絶対URLとは、http://localhost:8977/about/ のように、ホスト名とポートまで含めて書かれたURLを指します。これがファイルに残ると、公開先でもローカルの住所を指します。
WordPressの公式ドキュメント「Migrating WordPress」では、サイトのアドレスは設定の siteurl と home に入っています。
ただし同じページに、アドレスを変えても古いドメインや場所への参照はデータベースに残る、と書かれています。
また、本文の画像のリンクは投稿の本文(wp_posts の post_content)に保存されている、とあります。ウィジェットやプラグインの設定にも、古いURLが残ることがあるとされています。
僕の会社で困ったのは、ローカルで作っている途中にポートを変えていた点でした。最初のポートで作ったナビの項目や本文の画像は、そのポートのURLのまま保存されていました。
古いURLが残る場所と、拾い方を並べます。どれも「いまのURL」で探すと見つかりません。
| 残る場所 | どう残るか | 拾い方 |
|---|---|---|
| ナビの項目(メニュー) | 作った時点のポートの絶対URL | 書き出したHTMLを「localhost」で検索 |
| 本文の画像・リンク | 投稿の本文に絶対URLで保存 | 置換をポートを問わない形にする |
| スクリプト・JSONの中 | スラッシュの前に \ が入った形 | 置換の対象に、この形も入れる |
| URLの引数の中 | localhost%3A8977 のように記号が変換された形 | 置換の対象に、この形も入れる |
僕なら、まず書き出した後のファイルを広い語で検索します。データベースのどこに残っているかを探すより、出荷するファイルで確かめる方が漏れが少ないと考えています。
localhostのリンクを直す手順:ポートを問わない置換と、書き出し後の検索
直し方の要は、置換を特定のポートではなく localhost:<どのポートでも> で行い、書き出した後に「localhost」という広い語で残りを探すことです。
- 書き出したファイル全部を「localhost」で検索する。 HTMLだけでなく、CSS・JavaScript・XML・JSONも対象にします。見つかった行のポート番号を全部メモします。
- 置換を、ポートを問わない正規表現に書き換える。 普通の形に加えて、
\/の入った形と、記号が%3A・%2Fに変換された形も対象にします。 - データベースで直すなら、先に --dry-run で件数を見る。 WP-CLIの
wp search-replaceは、--dry-runで保存せずに結果だけを表示できます。 - 書き出し直して、1と同じ検索をもう一度かける。 0件になるまで、アップロードに進みません。
- 公開後に、全ページと全部の内部リンクを実際に開く。 手元で見つからなかった壊れ方を、公開先で拾います。
書き出したファイルに置換をかける形は、Pythonなら次のとおりです。フォルダ名と公開先のURLは例です。
import re, pathlib
NEW = "https://example.com"
rules = [
(re.compile(r"https?://localhost(?::\d+)?"), NEW),
(re.compile(r"https?:\\/\\/localhost(?::\d+)?"), NEW.replace("/", "\\/")),
(re.compile(r"https?%3A%2F%2Flocalhost(?:%3A\d+)?", re.I),
"https%3A%2F%2Fexample.com"),
]
for f in pathlib.Path("dist").rglob("*"):
if f.suffix not in (".html", ".css", ".js", ".xml", ".json"):
continue
text = f.read_text(encoding="utf-8", errors="ignore")
for pat, new in rules:
text = pat.sub(lambda m, new=new: new, text)
f.write_text(text, encoding="utf-8")
(?::\d+)? は「コロンと数字が続いてもよい」という意味です。8977でも8990でも、ポートが無くても同じように拾います。
データベースの側で直すときは、WP-CLIの wp search-replace が使えます。公式ドキュメントでは、PHPのシリアライズされたデータも正しく扱い、主キーの値は変えないとされています。
--regex で正規表現も使えますが、公式には通常の15〜20倍ほど時間がかかると書かれています。ポートの数が分かっているなら、1つずつ普通の置換をかける方が早いと考えています。
注意したいのはGUIDの列です。公式ドキュメントは、GUIDの列はどんな場合も変えないよう強く書いています。URLの置換の例にも、--skip-columns=guid が付いています。
僕の会社で起きた例:8990だけを置換し、導入時の8977が全ページのナビに残った
ここからは、僕の会社の失敗の記録からの転記です。お客さん向けに作っていたサイトなので、サイトの種類や中身が分かる情報は伏せています。
2026年8月10日、ローカルのWordPressで作ったサイトを、静的なHTMLに書き出して公開しました。作業をしたのは、僕の会社のAIアシスタントです。
書き出しのスクリプトでは、URLの置換を localhost:8990 の決め打ちで書いていました。8990は、そのとき使っていたポートです。
ところが、導入した時点のポートは 8977 でした。ナビの項目と本文の画像には、8977の絶対URLがデータベースに焼き付いていて、置換をすり抜けました。
その結果、全ページのグローバルナビが、動いていないローカルのURLを指したまま公開されました。それを指摘したのは僕です。
僕のMacでは、そのポートで別のプロジェクトのサーバーが動いていました。そのため、ナビを押すと、まったく関係の無いデモのページに飛びました。
いちばんまずかったのは、検証のやり方です。確認の検索も localhost:8990 に絞っていたので、結果は0件でした。置換と検査が同じ思い込みでできていたので、検査が何も守っていませんでした。
静的化のリンク切れを再発させない:検査は「localhost」の広い語で当て、ひっかかったら止める
再発を防ぐ要は、検査を変わりうる値ではなく広い語で当てることと、ひっかかったらスクリプトが自分で止まるようにすることの2つです。僕の会社では、この件のあと次の4つを決まりにしました。
- 置換は
localhost:<任意のポート>の正規表現で行う。\/の入った形と、記号が変換された形も対象にします。 - 書き出しスクリプトの最後に、出荷前のチェックを入れる。 「localhost」の残りと、noindexの有無を調べます。
- ひっかかったら
exit 1で終わり、アップロードに進ませない。 人が見落としても、ファイルが外に出ないようにするためです。 - 公開後に、全ページと全部の内部リンクを実際に開く検査を回す。
出荷前のチェックは、短いスクリプトで足ります。「localhost」という語そのものは記号に変換されないので、3つの形をまとめて拾えます。
import pathlib, sys
hits = []
for f in pathlib.Path("dist").rglob("*"):
if f.suffix in (".html", ".css", ".js", ".xml", ".json"):
text = f.read_text(encoding="utf-8", errors="ignore")
if "localhost" in text:
hits.append(str(f))
if hits:
print("localhost が残っています:", *hits[:20], sep="\n")
sys.exit(1)
print("OK")
検査の語を広くすると、関係の無い箇所が見つかることもあります。それでも僕は、見落とすより、広い語で当てる方を選んでいます。今回の事故は、狭い語で探して0件と出たことが原因だったからです。
置いたファイルそのものが壊れる事故もあります。アップロードで中身が空になった例は、curlのFTPSアップロードが0バイトになる時の直し方に書きました。
業務で静的化をAIやスクリプトに任せるときの注意
置換と検査を、同じ値から作らない。 書いた人と同じ思い込みで検査を作ると、今回のように検査が0件を返して終わります。検査は、置換より広い語で当てます。
途中でポートやドメインを変えたら、その全部を記録しておく。 変えた履歴があれば、置換の対象を後から洗い出せます。残っていなくても、上の広い語の検査で拾えます。
手元のMacで動作確認するときは、ほかのサーバーが動いていないかに気をつける。 僕の会社の例では、別のサーバーが動いていたせいで、壊れたリンクが別のページを開いて見えました。
お客さんのサイトでは、公開の前に検査の結果を残す。 何を何件調べ、0件だったかを記録すれば、後から確かめられます。
公開した後に見た目が古いままのときは、別の原因もあります。CSSが残っていた例は、CSSが反映されない原因とキャッシュの直し方にまとめました。
置いたPHPがそのまま表示された例は、.htaccess の SetHandler で PHP のソースが表示される原因にあります。
「静的化したサイトのリンクが一部おかしい」「書き出しの手順を安全に作り直したい」というときは、下のフォームからご相談ください。今の手順の確認から一緒に見ます。
よくある質問
設定のWordPressアドレスを変えれば、本文のURLも変わりますか?
変わりません。WordPressの公式ドキュメントでは、アドレスを変えても古いドメインや場所への参照はデータベースに残るとされています。本文の画像のリンクは、投稿の本文の中に保存されています。静的化の前に、置換か書き出し後の検査で残りを拾う必要があります。
wp search-replaceでGUIDの列も置き換えていいですか?
置き換えません。公式ドキュメントは、GUIDの列はどんな場合も変えないよう強く書いています。フィードリーダーが記事を見分けるのに使うためです。公式の例でも、URLの置換には --skip-columns=guid を付けています。
静的化した後、localhostが残っていないかはどう確かめますか?
書き出したファイル全部を「localhost」という広い語で検索します。ポート番号まで入れた語で探すと、別のポートの残りを見落とします。僕の会社では、1件でも見つかったらアップロードを止める形にしています。