Next.jsとmicroCMSの公開・運用手順:WordPressからの移行で改ざんリスクを減らす
Next.jsとmicroCMSで静的サイトを構築し、既存サーバーへ公開し、WordPressからの移行で改ざんリスクを減らす手順。APIキー管理、初回公開、自動更新、監視、バックアップ、復旧まで解説。
WordPressで運用する情報サイトを、Next.jsの静的出力、既存のWebサーバー、microCMSへ移行する設計例を紹介します。公開ページを作る処理と、閲覧者へ配信する処理を分けることで、公開側にPHP・WordPress管理画面・プラグイン・データベースを置かない構成にできます。
これは改ざんリスクを減らす対策です。CMSやGit、サーバー管理のアカウントが侵害されれば、正規の公開経路を通じて改ざんされる可能性は残ります。この記事では、構築、初回デプロイ、更新の自動化、日常点検、復旧までを一連の手順として説明します。構成の設計・導入ガイドであり、特定のクラウドへの移行は前提にしていません。
1. なぜ静的配信が改ざん対策になるのか
公開側から減らせる攻撃経路
典型的なWordPress構成では、閲覧時にWebサーバー上のPHPが動き、テーマやプラグイン、データベースと連携します。本体・プラグインの脆弱性、管理者アカウントの不正利用、ファイル書き込み権限などを適切に管理する必要があります。
静的出力では、ビルド時に記事をHTMLへ変換します。公開側は生成済みのHTML・CSS・JavaScript・画像を配信し、閲覧リクエストごとにWordPressのPHPやDBを動かしません。この構成では、公開WordPressの実行環境を入口とするサーバー側の攻撃経路を取り除けます。
移行しても残るリスク
- microCMSの編集権限が盗まれると、不正な記事が公開される可能性がある。
- GitやCIの権限が盗まれると、不正なコードがビルドされる可能性がある。
- サーバー管理やDNSの管理権限が盗まれると、配信先や公開ファイルを変更される可能性がある。
- 依存パッケージ、埋め込みスクリプト、広告・解析タグの侵害は静的サイトでも影響する。
- CMS本文を無条件にHTMLとして挿入すると、保存型XSSにつながる可能性がある。
WordPressそのものが必ず危険という意味ではありません。更新・権限分離・バックアップを適切に行う方法もあります。移行の効果は、不要な実行環境を減らし、編集・ビルド・配信の権限を分ける設計とセットで評価します。
2. 構成図と公開までの流れ
microCMSは編集、Next.jsは生成、Webサーバーは配信
- 編集者がmicroCMSで原稿を作成し、公開する。
- ビルド環境が読み取り専用のAPIキーで公開記事を取得する。
- Next.jsがHTMLと関連ファイルを
outへ生成する。 - 検証済みの成果物をWebサーバーへ配置する。
- 閲覧者は公開済みのファイルを受け取る。
この例では閲覧者のブラウザーからmicroCMSへ記事取得APIを直接呼びません。CMSが一時的に使えなくても、すでに生成・配信されたページはCMSへの実行時問い合わせに依存しません。ただし、新しい記事のビルドや、外部サービスに依存するフォームなどは別です。
静的出力とSSR・ISRの違い
本記事は output: 'export' による静的出力を対象とします。記事の更新には再ビルドと再デプロイが必要です。サーバーで実行するSSRやISRとは構成・更新方法・守る対象が異なります。静的出力のまま、サーバー専用機能やオンデマンド再検証が使えると考えないでください。
会員ごとのページ、注文処理、認証付きプレビューなどが必要な場合は、別APIやSSRを含めて設計します。その追加機能には別途アクセス制御が必要です。
3. 移行前にWordPressの機能とURLを棚卸しする
移す内容と代替する機能を決める
- 記事・固定ページ・画像・添付ファイル・著者・公開日を一覧にする。
- パーマリンクと新URLの対応表を作り、旧URLの転送を設計する。
- フォーム、検索、コメント、会員機能、予約投稿、プレビューを洗い出す。
- テーマやプラグインが本文へ挿入したショートコード・HTMLを確認する。
- タイトル、説明文、canonical、構造化データ、サイトマップを移す。
画像が旧WordPressサーバーだけに残っていると、停止後に表示されなくなります。microCMSのメディアや別の配信先へ移し、本文内のURLも更新します。移行作業に着手する前に、旧環境のデータベースとアップロードファイルを復元可能な形で保存してください。バックアップの取得方法・検証方法は13節で扱います。
切り替え後も旧管理画面を放置しない
旧サイトの公開停止は、URLの転送だけでは完了しません。公開IPや別ホスト名から古いPHP・管理画面へ到達できる状態も確認します。保管が必要な場合は用途に応じてアクセスを制限し、不要な稼働環境は切り替え検証後に停止します。
4. microCMSのAPIと権限を準備する
記事用APIを作る
microCMSでリスト形式のAPIを作り、エンドポイントを例として articles にします。最低限 title と body を用意し、必要なら説明文・画像・スラッグを追加します。下書きと公開済みの扱いを決め、まず公開済みのテスト記事を1件用意してください。
以下のコードで使う articles は説明用です。既存サービスのエンドポイントに合わせて置き換えます。API名とコンテンツIDは別のもので、既存のIDを変更するときは公開URLへの影響も確認します。
公開ビルド用キーと管理用キーを分ける
公開ビルドでは、必要な記事APIをGETする権限だけを持つキーを使います。記事の作成・変更・削除の権限を持つ管理用キーを流用しないでください。microCMSのキー設定で対象APIと操作権限、公開状態に関する設定を確認します。
GET専用キーであっても、下書きや限定公開のコンテンツまで取得できる設定になっていないか、必ずAPI設定で確認してください。下書き取得やプレビューに必要な権限は本番の静的ビルド用キーから分離し、ビルドが取得するデータの範囲を最小限にします。
5. Next.jsを静的出力に設定する
next.config.jsを設定する
既存設定へ必要な項目を追加します。以下はCommonJS形式の例です。ES Modules形式のプロジェクトでは、そのプロジェクトの設定ファイル形式に合わせます。
/** @type {import('next').NextConfig} */
module.exports = {
output: 'export',
trailingSlash: true,
images: { unoptimized: true },
};trailingSlash: true では、たとえば /about/ に対応するファイルが out/about/index.html になります。標準の画像最適化サーバーを使わない例として unoptimized を指定していますが、画像の軽量化・寸法指定は別途行います。
Next.jsとNode.jsは、利用する機能と使用するビルド環境で対応する、サポート対象のバージョンを選びます。古いプロジェクトを移す場合は、移行と依存関係の更新を分けて検証し、使ったバージョンとロックファイルを記録します。
APIキーをクライアントへ渡さない
ローカルではGit管理対象外の .env.local に必要な設定を置きます。以下はプレースホルダーで、実際の値を記事・Git・共有ログへ貼り付けません。
MICROCMS_SERVICE_DOMAIN=your-service
MICROCMS_API_KEY=your-read-only-keyNEXT_PUBLIC_MICROCMS_API_KEY のように公開用接頭辞を付けないでください。また、接頭辞がなくてもAPIキーをページのpropsへ含めたり、HTMLに埋めたりすれば漏えいします。ビルド時に利用する認証情報と、閲覧者へ返す記事データを分けます。
6. microCMSから記事を取得してページを生成する
Pages Routerでの最小例
次はPages Routerの pages/index.jsx に置く一覧の例です。microcms-js-sdk を導入し、エンドポイント articles に title フィールドを作成した前提です。App Routerではデータ取得の書き方が異なり、getStaticProps は使いません。
import { createClient } from 'microcms-js-sdk';
export async function getStaticProps() {
const serviceDomain = process.env.MICROCMS_SERVICE_DOMAIN;
const apiKey = process.env.MICROCMS_API_KEY;
if (!serviceDomain || !apiKey) {
throw new Error('microCMSのビルド用設定が不足しています');
}
const client = createClient({ serviceDomain, apiKey });
const articles = [];
let offset = 0;
while (true) {
const page = await client.getList({
endpoint: 'articles',
queries: { limit: 100, offset, fields: 'id,title' },
});
articles.push(...page.contents);
offset += page.contents.length;
// ページ取得中に記事が公開・削除されるとtotalCountが変化する場合があります。
// 厳密な整合性が必要な場合は、取得開始時点のtotalCountを保持して比較するか、
// ビルド対象のリビジョンを固定する方式を検討してください。
if (offset >= page.totalCount) break;
if (page.contents.length === 0) {
throw new Error('記事の取得が途中で停止しました');
}
}
// APIキーやクライアント自体はpropsに含めない
return { props: { articles } };
}
export default function Home({ articles }) {
return (
<main>
<h1>記事一覧</h1>
<ul>
{articles.map(article => (
<li key={article.id}>{article.title}</li>
))}
</ul>
</main>
);
}公開APIの最大件数を超える一覧では、ページ分割して全件取得します。詳細ページはPages Routerなら getStaticPaths と getStaticProps でビルド対象を列挙し、静的出力に対応する構成にします。この最小例は一覧タイトルだけを表示し、本文をHTMLとして挿入しません。
リッチテキストは無条件で信用しない
本文を dangerouslySetInnerHTML などで表示する場合は、許可するタグ・属性・URLの方式を定義し、保守されているサニタイザーなどで処理します。CMS管理者が入力した内容でも、アカウント侵害を想定すると無条件には信頼できません。正規表現による単純なタグ削除だけをXSS対策にしないでください。
7. ローカルでビルドして公開物を検査する
outを作る
npm ci
npm run buildnpm ci にはコミット済みのロックファイルが必要です。成功後は out/index.html、記事詳細、画像、CSS、JavaScript、404.html、サイトマップを確認します。CMSの取得に失敗した場合は、空の記事一覧を正常なビルドとして公開しないようにします。
公開前の合格条件を決める
- 一覧と詳細ページの件数が期待どおりで、下書きが混ざっていない。
- 代表的な記事URLを直接開ける。
- APIキー、
.env、認証情報、内部用バックアップが出力へ含まれていない。 - スマートフォン幅でもコードや画像が本文からはみ出さない。
- canonicalとサイトマップが本番ドメインを指している。
- 存在しない記事は、本文付きのトップページを200で返すのではなく、適切に404になる。
8. 既存のWebサーバーへ初回公開する
配置先と公開権限を分ける
ApacheやNginxなど、静的ファイルを配信できるWebサーバーを使います。公開するのは out の中身です。プロジェクト全体、.env.local、CMSのバックアップ、SSH鍵は公開ディレクトリへ置きません。
公開先のディレクトリはサイト専用にし、Webサーバーの実行ユーザーには原則として読み取り権限だけを与えます。転送担当者は専用アカウント・SSH鍵を使い、配置に必要な範囲だけ書き込みできるようにします。OS・Webサーバー・SSHの更新も運用に含めます。
一時ディレクトリへ転送して確認する
次は説明用のホスト名とパスです。SSHで接続でき、配置担当者がステージへ書き込める前提です。初回接続時にはサーバー管理者から別経路で入手したホスト鍵の指紋と照合します。
ssh deploy@example.com 'mkdir -p /srv/site/staging'
rsync -rltvz --dry-run out/ deploy@example.com:/srv/site/staging/
rsync -rltvz out/ deploy@example.com:/srv/site/staging/
rsync -rln --checksum --itemize-changes out/ deploy@example.com:/srv/site/staging/最後の比較で転送対象に差分が出ないことを確認します。out/ の末尾のスラッシュは「ディレクトリの中身」を送る指定です。-r は再帰的にディレクトリをコピーし、-l はシンボリックリンクをリンクのまま保持し、-t はファイルの更新日時を保持し、-v は転送内容を表示し、-z は転送時に圧縮します。いずれも壊れやすい設定ではありませんが、意味を理解せずにオプションを追加・削除しないでください。この例では削除を行わないため、古いステージを再利用すると過去のファイルが残ります。運用ではリリースごとに新しい空のステージを作り、内容を検証してください。
バックアップ後に公開先へ反映する
既存の公開物を公開ディレクトリ外へ保存し、対象サイトの設定と配置先を再確認してから反映します。段階的なコピー中にHTMLとJavaScriptの組み合わせが変わる場合があるため、継続運用ではリリース別ディレクトリと公開先の切り替えを設計すると、差し戻しもしやすくなります。シンボリックリンクを使う方式では、Webサーバー設定や権限も検証が必要です。
共有の公開ディレクトリに対して rsync --delete を無条件で実行しないでください。一方、削除なしの上書きだけでは公開停止した記事が残ります。削除対象URLを確認し、サイト専用の成果物の切り替え、または承認済みの個別削除で対応します。
9. ビルド環境の認証情報を保護する
APIキーと転送用のSSH鍵を分離する
microCMSの読み取り用APIキーはビルド環境だけに置きます。配置専用のWebサーバーには、記事生成を行わない限りこのキーは不要です。ローカルの .env.local はGit管理から除外し、共有端末では読み取り権限も制限します。CIを使う場合は、そのサービスのシークレット保管機能を利用します。
APIキーとSSH鍵をソースコード、成果物、ログ、スクリーンショットへ出さないようにします。漏えいした場合はファイルから消すだけでなく、認証情報を無効化して再発行します。公開ビルド用キー、CMS編集者の権限、サーバーの配置権限を別々に見直せる構成にします。
ビルドに渡すコードにも権限がある
ビルド中に動く依存パッケージやスクリプトは環境変数を読めます。秘密値を非表示にするだけでは十分ではありません。保護された本番ブランチ、変更レビュー、依存関係の更新、未確認のプルリクエストへシークレットを渡さない設定を組み合わせます。
10. microCMSの記事更新を公開へ反映する
最初は手動更新で一連の流れを確立する
- microCMSで原稿を確認し、記事を公開・更新する。
- ビルド担当者が公開対象のコードとCMSの更新内容を確認する。
npm ciとnpm run buildを実行する。- 生成結果を検証し、ステージへ転送して内容を比較する。
- 現在の公開物を保存し、検証した成果物を公開先へ配置する。
- 公開URLで本文・一覧・画像・サイトマップを確認し、完了を記録する。
microCMSで公開しただけでは、静的サイトのHTMLは更新されません。ビルド・転送が失敗した場合は以前の正常な公開物を維持し、編集者には「CMS更新済み・サイト反映待ち」と分かるようにします。
自動化する場合は認証付きの受信処理を用意する
手動の流れが安定したら、microCMSのWebhookからCIなどのビルド処理を開始する構成へ拡張できます。静的ファイルだけを配信するサーバーではWebhookを処理できないため、別の受信APIやCIの起動口が必要です。提供される認証方式に合わせて送信元の正当性を検証し、公開URLに認証情報を載せないようにします。
公開・更新・公開停止・削除を対象に、テスト記事で送信結果とビルド結果を確認します。重複通知や連続更新に備え、処理の集約と同時デプロイの防止を入れます。古いビルドが後から完了して新しい記事を上書きしないよう、公開処理は直列化し、対象リビジョンを確認します。CIのジョブ同時実行数を1に制限する、デプロイ用のロックファイルやキューを使う、公開処理の開始時にビルド対象のコミットIDとCMSの更新時刻を記録して古い結果を破棄するなど、利用するCI環境に合わせた方法で実現します。
11. URL・HTTPS・WordPressからの切り替えを確認する
記事の直接アクセスと404を確認する
trailingSlash: true の出力では、Webサーバーが各ディレクトリの index.html を返せるようにします。トップだけでなく、深い記事URLを新しいタブで直接開いてください。存在しないURLにはHTTP 404を返し、カスタム404ページを使う場合も応答コードを保ちます。
curl -I https://www.example.com/
curl -I https://www.example.com/articles/sample/
curl -I https://www.example.com/not-found-test/これは応答ヘッダーの確認です。本文・画像・リンク・モバイル表示はブラウザーでも確認します。静的な複数ページを、すべてトップの index.html へ200で書き換えるSPA向けルールにしないでください。
同じサーバーを使う場合も旧環境を整理する
既存のドメインとサーバーを使うなら、記事公開のためだけにDNSを変更する必要はありません。HTTPS証明書の更新、www の統一、canonical、サイトマップ、旧記事から新記事への301転送を確認します。旧WordPressのPHPや管理画面が別パス・別ホスト名で公開されたままなら、移行の効果が限定されます。バックアップと復元手順を確保し、不要な実行環境を停止・隔離してください。
12. 日常運用で守ること
公開のたびに確認する
- 変更した記事、一覧、画像を公開URLで確認する。
- 意図しない削除や下書きの公開がないか確認する。
- コミットID、CMS更新時刻、ビルド番号、公開日時を記録する。
- 成功した成果物のZIPやハッシュ、CMSのバックアップを保管する。
定期点検する
- サーバー管理・Git・microCMSの多要素認証と、退職者・外部委託先の権限を見直す。
- Node.js、Next.js、依存パッケージの更新と脆弱性情報を確認する。
- ビルド失敗、HTTP異常、主要ページの内容変化を通知する仕組みを確認する。
- ビルド回数、配信量、CMSのAPI利用量と予算アラートを確認する。
- DNS・Git・サーバー管理の操作ログと、利用プランで使えるCMSの履歴を確認する。
- CSPなどのヘッダーを外部スクリプト構成に合わせて設計し、適用前に影響を検証する。
料金・利用上限・提供機能は変わるため、契約時点の公式情報で確認します。請求通知を設定しても、支出が自動的に停止するとは限りません。
13. バックアップと改ざん時の復旧
Gitだけでは記事データを復元できない
Gitはアプリのコードを管理します。CMSの記事データ、画像、APIスキーマ、環境設定、公開済み成果物は別に保管します。履歴の保持期間や復元機能はサービス・プランに依存するため、自分たちの復旧条件を満たすか確認してください。バックアップの取得だけでなく、検証環境での復元練習が必要です。
疑わしい更新を見つけたときの順序
- 公開内容、ログ、時刻、ビルド番号を保全する。
- 不正更新の継続を止めるため、自動ビルドや侵害されたアカウントの利用を制限する。
- 正常と確認できた成果物を再配置し、公開URLで復旧を確認する。
- CMS・Git・サーバー管理・DNSのどこから変更されたかを調査し、該当する権限と認証情報を更新する。
- 原因を除去したうえで、原稿・ソースを修復して再ビルドする。
- 再発監視と、必要な関係者への報告を行う。
不正な記事がCMSに残っている状態で過去のコードだけを再ビルドすると、同じ内容が再び出力されます。コードのロールバック、CMSデータの復元、公開成果物の差し戻しを区別してください。
14. 導入完了の判断と参考資料
デプロイ成功だけで完了にしない
初回公開、記事更新、公開停止、ビルド失敗時の通知、正常な成果物への復旧まで試してから運用へ移します。静的配信による攻撃経路の削減と、管理権限・更新経路の保護がそろって、改ざんへの備えになります。
公式資料
関連する記事
Googleの優先するニュース提供元にSEの部屋を追加
Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。
候補にSEの部屋が表示されない場合は、まだ追加できません。