タグ: Dependabot

  • 上げる先が、ひとつしかなかった

    上げる先が、ひとつしかなかった

    脆弱性の警告が28件。そのうち23件が next に集約されることまでは分かっていました。あとは上げるだけ——のはずが、まず「どこまで上げるか」で手が止まりました。

    この記事で扱うこと

    更新先のバージョンをどう選んだか、自動化ツールがやってくれたこととやってくれなかったこと、そして「入れる必要のない設定」を消した判断。メジャーバージョンを2つ跨ぐ更新を、確認しながら進めた記録です。

    まず、選択肢を数える

    手元は 14.2.35 でした。自動で提案されていたのは 16.3.1。2世代ぶんの差があります。

    最初に考えたのは「まず14系の最新まで上げて、それで消える分を消そう」でした。メジャーを跨がなければ壊れにくいからです。これは実行しませんでした。調べたら、前提が違っていたからです。

    サポート状況
    Next.js 14  サポート終了(2025-10-26)
                最終パッチ 14.2.35 ← 手元にあるのがこれ
    
    Next.js 15  Maintenance LTS  2026年10月まで
    Next.js 16  Active LTS       2027年10月まで

    14系の最新は、すでに手元にありました。「上げる」という操作に余地が無く、18件の警告は14に留まる限り1件も消えません。

    やっても何も起きないコマンドを、実行しかけた

    調べる前は「14系の最新に上げましょう」と判断していました。実行すれば何かが変わるはず、という思い込みです。手を動かす前にサポート期限を1つ調べただけで、この無駄が消えました。作業の前に前提を確認するのは、遠回りに見えて速いです。

    15を選ばなかった理由

    残る選択肢は15と16です。15の方が変更が小さいので、一見こちらが安全に見えます。

    しかし15のサポートは2か月後に切れます。そこで上げても、すぐ同じ作業をやり直すことになります。「変更が小さい」ことの価値は、その変更が長持ちするかどうかで決まります。2か月で無効になるなら、小さいことに意味がありません。

    実質的に、上げる先は16しかありませんでした。

    何が壊れるかを、先に調べる

    移行ガイドを読んで、このサイトに該当するものだけを抜き出しました。全部を読むのではなく、自分のコードに関係するものを探すという読み方です。

    変更点 このサイトへの影響
    params が非同期になる 該当。記事ページで同期的に使っている
    React 19.2 が必要 該当。18.3.1 から上げる必要がある
    next lint の廃止 該当。package.json に書いてある
    画像設定の既定値変更 明示指定していないので影響なし
    Turbopackが既定に webpack設定を持っていないので影響なし
    middleware の改名 使っていない

    該当したのは3つだけでした。2世代ぶんの変更に見えて、実際に手を入れる場所は限られていると分かった時点で、作業の見通しが立ちます。

    自動化ツールは、半分やってくれる

    Next.jsには移行用のコードモッド(コードを機械的に書き換えるツール)が用意されています。2つ実行しました。

    実行したもの
    # パッケージ更新・設定ファイルの書き換え・lintの移行
    npx @next/codemod@canary upgrade latest
    
    # params / searchParams の非同期化
    npx @next/codemod@canary next-async-request-api .

    変換結果は正確でした。たとえば記事ページはこうなります。

    自動変換された箇所
    - type Props = { params: { slug: string } };
    + type Props = { params: Promise<{ slug: string }> };
    
    - export default async function BlogPostPage({ params }: Props) {
    + export default async function BlogPostPage(props: Props) {
    +   const params = await props.params;

    49ファイルを走査して2ファイルを変更、エラー0。手で直していたら見落としが出る種類の作業です。

    変換されなかった場所も、確認する

    「0 errors」は「全部やった」ではありません。ツールが認識できなかった書き方は、静かに素通りします。変更されたファイル数と、自分が想定していた箇所の数が合っているかは、自分で確かめる必要があります。今回は2ファイルで一致していました。

    入れる必要のない設定を、消した

    ここが今回いちばん判断が要った箇所です。

    コードモッドは、7つのファイルにこれを追加していました。

    追加されていたもの
    // TODO: Cache Components adoption. Refactor this route so this
    // opt-out can be removed.
    export const instant = false;

    調べると、これは16.3の新機能に対するオプトアウトで、別の設定を有効にしているときにだけ意味を持ちます。このサイトはその設定を使っていないので、7ファイルすべてで何もしていません。

    将来のために先回りして入れてくれたものです。しかし残すとどうなるかを考えました。

    残す場合 消す場合
    将来その機能を有効にしたとき、警告が出ない そのとき改めて入れる必要がある
    「あとで対応する」というTODOが7か所に残る コードに無効な設定が残らない

    消しました。誰も対応しないTODOは、そのうち景色になります。読むたびに「これは何だっけ」と確認するコストが7か所ぶん発生し、いずれ誰も読まなくなります。必要になったときに、必要な場所へ入れるほうが健全だと判断しました。

    自動生成されたものを、そのまま受け取らない

    コードモッドが入れたものは正しい提案です。しかし「このプロジェクトにとって必要か」までは判断していません。ツールは一般的に安全な側へ倒します。プロジェクトの事情を知っているのは書き手のほうです。

    本番に入れる前に、本番と同じもので確かめる

    今回はmainに直接入れず、ブランチを切りました。

    進め方
    git checkout -b feat/next-16
    (コードモッド実行・手直し)
    npm run build      ← 手元で通ることを確認
    git push -u origin feat/next-16

    ブランチをpushすると、デプロイ先がプレビュー用のURLを作ってくれます。本番と同じビルド手順で、本番と同じ環境に載った状態を、公開せずに触れます。

    そこで確認したのは6点です。

    確認項目
    1. 記事一覧が20本、サムネイルが出る
    2. 記事ページの本文・目次・読了バー
    3. sitemap.xml が25件(数を数える)
    4. robots.txt が4行
    5. 構造化データの日時に Z が付いている
    6. ビルド時のエラー・警告
    3番は、数えないと分からない

    sitemapは、失敗しても固定ページだけを返して200で応答する作りにしてあります。壊れていても画面は正常に見えます。「表示された」ではなく「25件あった」まで確認しないと、記事がsitemapから消えていても気づけません。

    全部通ったのを見てから、mainへマージしました。プレビューで通ったものが本番でも通る——確認の順番として、これが一番安く済みます。

    結果

    脆弱性の件数
    next     18件 → 0
    postcss   4件 → 0
    nanoid    1件 → 0
    glob      3件 → 0
    lodash    2件 → 2   (開発用のみ)
    
    合計     28件 → 2件

    glob が消えたのは想定外でした。ESLintも一緒に上がり、その依存として新しい glob が入ったためです。本番で動くもののうち、既知の脆弱性は無くなりました。

    残った2件は開発用の依存で、配信される成果物には入りません。

    今回の学び

    まず、「上げる」の前に、上げ先を数えること。14系の最新はすでに手元にあり、実行しても何も起きないコマンドを打ちかけていました。サポート期限を1つ調べるだけで防げます。

    次に、変更の小ささより、その変更が持つ期間で選ぶこと。15は変更が小さいですが2か月で切れます。安全に見える選択肢が、実は先送りなだけということがあります。

    そして、自動化ツールの出力も、読んでから受け取ること。コードモッドは正確でしたが、このプロジェクトに不要なものも入れていました。ツールは一般的に安全な側へ倒し、個別の事情までは知りません。

    最後に、本番と同じもので確かめてから本番に入れること。プレビュー環境を1回挟むだけで、「マージしてから気づく」が「マージ前に気づく」に変わります。確認の順番を変えただけで、リスクの大きさが変わります。

  • 依存の中の依存

    依存の中の依存

    pushしたら、GitHubからこう返ってきました。

    push時のメッセージ
    remote: GitHub found 29 vulnerabilities on kisaku39/kisaku-portfolio's
    remote: default branch (13 high, 14 moderate, 2 low).

    29件。数字だけ見ると、手のつけようがないように感じます。ところが中身を分類すると、実質3つの問題でした。

    この記事で扱うこと

    警告の件数をそのまま受け取らずに読み替える方法、自動で出てくる更新PRの中で「そのまま入れてよいもの」の見分け方、そして「直接の依存を上げたのに警告が消えない」という現象の正体。

    件数は、そのままの意味ではない

    一覧を開いて、パッケージ別に数え直しました。

    パッケージ 件数 用途
    next 19 本番で動く
    postcss 5 ビルド時に動く
    lodash 2 開発用のみ
    glob 1 開発用のみ
    nanoid 1 本番で動く

    29という数は、1つのパッケージに対する報告が積み上がった結果でした。nextの19件は、SSRF、Server ComponentsのDoS、画像最適化APIのDoS、キャッシュ混同——別々の問題ですが、直すのは「nextを上げる」の1手です。

    件数ではなく、手数を数える

    29件を29個の作業だと思うと、着手する気力が湧きません。実際に必要な操作は5つ以下でした。警告システムは問題の個数を報告しますが、こちらが知りたいのは作業の個数です。ここを読み替えないと、量に圧倒されて何もしないことになります。

    開発用かどうかで、優先度が変わる

    一覧には Development という表示が付くものがあります。lodash と glob がそれでした。

    これらはビルドや開発中にしか動かず、配信される成果物には入りません。つまり、サイトを見に来た人がこの脆弱性に触れることはありません。悪用するには、まず開発環境に入り込む必要があります。

    危険がゼロという意味ではありませんが、本番で動くものと同じ緊急度で扱う必要はありません。3件がここに該当したので、後回しにしました。

    自動で出てくるPRは、同じ安全さではない

    Dependabotは修正用のPRを自動で作ってくれます。5本ありました。

    開いていたPR
    #7   next               14.2.35 → 16.3.1    メジャー2つ跨ぎ
    #5   eslint-config-next 14.2.35 → 16.3.1    メジャー2つ跨ぎ
    #6   tailwindcss        3.4.4   → 4.3.3     メジャー跨ぎ
    #8   graphql-request    6.1.0   → 7.4.0     メジャー跨ぎ
    #10  postcss            8.5.23  → 8.5.26    パッチのみ

    ボタンひとつでマージできます。しかし、同じ「更新」でも中身の危険度がまったく違います。

    バージョンの変化 意味 扱い
    8.5.23 → 8.5.26 不具合修正のみ。使い方は変わらない そのまま入れてよい
    6.1.0 → 7.4.0 使い方が変わっている可能性がある 動作確認が要る
    14.2.35 → 16.3.1 2世代ぶんの変更が入る 移行作業として扱う
    GitHub上でマージすると、確認の機会が無い

    手元で更新する場合は npm run build で通ることを確かめてからpushできます。PRをブラウザでマージすると、その工程が丸ごと飛びます。通るかどうかは、本番のデプロイが動き出してから分かります。パッチ更新なら許容できますが、メジャー更新でこれをやるのは賭けです。

    そこで、#10だけをマージしました。チェックも2つとも通っていたので、これは安全な1本です。

    1件しか減らなかった

    マージ後、件数を数え直しました。

    結果
    29件 → 28件
    
    postcss  5件 → 4件

    postcssを上げたのに、postcssの警告が4件残っています。バージョンは 8.5.26 になっているはずです。

    残っている警告を1つ開くと、こう書いてありました。

    アラートの詳細
    影響を受けるバージョン   8.5.11 以下
    修正されたバージョン     8.5.12

    8.5.26 は 8.5.12 より新しいので、条件に当てはまりません。それなのに警告が残っている。

    依存の中の依存

    ロックファイルを直接見て、答えが出ました。

    package-lock.json 内の postcss
    8.5.23 (dev)  node_modules/postcss                ← 8.5.26 に更新された
    8.4.31        node_modules/next/node_modules/postcss ← 手つかず

    postcssが2つ入っていました。

    1つは自分で指定したもの。もう1つは next が自分の内側に抱えているものです。npmは、あるパッケージが要求するバージョンと自分の指定が食い違うとき、そのパッケージの中に別のコピーを置きます。同じ名前のものが、違うバージョンで同居できる仕組みです。

    なぜそういう作りになっているのか

    もし1つしか置けないなら、「AはpostCSS 8.4を要求、BはpostCSS 8.5を要求」という状況で、どちらかが壊れます。入れ子にすることで、互いの要求を同時に満たしています。便利な仕組みですが、その代わり「上げたつもりが上がっていない」が起こります。

    自分で指定した依存は、自分で上げられます。他のパッケージが抱えている依存は、そのパッケージ自身が上げてくれるのを待つしかありません。

    同じことが nanoid にも起きていました。これも next 経由で入っています。

    読み替えた結果

    ここまでを踏まえて、28件を整理し直しました。

    分類 件数 必要な操作
    next 本体 18 next を上げる(1手)
    next が抱える postcss 4
    next が抱える nanoid 1
    開発用(lodash / glob) 5 本番に影響なし。後回し

    28件のうち23件が、1つの操作に集約されました。最初に見た「29件」という数字とは、受ける印象がまったく違います。

    それでも、一気には上げない

    では next を16に上げればいい——とはなりませんでした。

    14から16は、2世代ぶんの変更が入ります。このサイトはApp Routerを使い、ISRで事前生成し、つい先ほどsitemapと構造化データを入れたばかりです。それらが動かなくなる可能性を、確認せずに本番へ出すことはできません。

    段階を分けました。

    今日やること
    npm install next@14 eslint-config-next@14   # 14系の最新まで
    npm run build                               # 通るか確認
    npm run start                               # 本番と同じ条件で表示確認

    まずメジャーを跨がずに取れる分だけ取ります。これで消えない分が、16に上げる判断の材料になります。「全部消したい」から一気に上げると、落ちたときに何が原因か分かりません。

    確認は dev ではなく start で

    開発サーバー(npm run dev)と本番(npm run build → npm run start)では動きが違います。sitemapや構造化データのようにビルド時に生成されるものは、devで見ても本番と同じとは限りません。更新の影響を確かめる目的なら、本番と同じ手順で見る必要があります。

    今回の学び

    まず、警告の件数と、作業の件数は違うこと。29件のうち23件が1つの操作に集約されました。件数のまま受け取ると、量に圧倒されて着手できなくなります。パッケージ別に数え直すだけで、見える景色が変わります。

    次に、「更新」の一言に、危険度の違う操作が混ざっていること。パッチ更新とメジャー更新は、ボタンの見た目が同じでもやっていることが違います。自動で提案されたからといって、同じ扱いをしてよいわけではありません。

    そして、直接の依存を上げても、内側の依存は動かないこと。同じパッケージが違うバージョンで同居できる仕組みがあるため、「上げたのに消えない」が起こります。ロックファイルを見れば10秒で分かる話ですが、知らないと延々と悩みます。

    最後に、本番に届くものと、届かないものを分けること。開発用の依存は、同じ「高」でも読者に届きません。全部を等しく緊急扱いすると、本当に急ぐものに手が回らなくなります。

  • 公開したあとの話 — 脆弱性検知と監視の仕組みを作る

    公開したあとの話 — 脆弱性検知と監視の仕組みを作る

    ポートフォリオサイトを本番公開したあと、運用のための仕組みを整えました。脆弱性の検知、稼働の監視、依存パッケージの更新です。

    公開までの記事は「動かすまで」の話でしたが、今回は「動き続けさせる」ための話になります。地味な作業ですが、初回の実行でいきなり実害のある問題が見つかったので、やっておいてよかったと思っています。

    公開したら終わり、ではなかった

    デプロイが終わった時点で、次のような状態でした。

    ・使っているnpmパッケージ 679個 の脆弱性 → 誰も見ていない
    ・WordPress本体・プラグインの更新       → 気付いたときに手で確認
    ・公開してはいけないファイルの置き忘れ   → 気付く手段がない
    ・サイトが止まったとき                  → 自分が見に行くまで分からない

    個人サイトなので、止まっても誰も困りません。それでも仕組みを入れたのは、実際に「置き忘れ」をやらかしていたからです。

    移行作業のときに、データベース全体が入った約62MBのバックアップファイルをサーバーに置きました。作業には必要でしたが、URLさえ分かれば誰でもダウンロードできる状態でした。中にはユーザー情報もパスワードのハッシュも入っています。

    これは移行の途中で自分で気付いて削除しましたが、気付かなければ置きっぱなしでした。同じことがまた起きないようにしたい、というのが動機です。

    やったこと1: 脆弱性の自動検知を入れる

    GitHubには Dependabot という機能が最初から用意されています。使っているパッケージの一覧を読み取り、既知の脆弱性データベースと突き合わせて教えてくれるものです。

    設定は3段階に分かれていて、順に有効化する必要があります。

    機能 役割
    Dependency graph 依存関係を解析する土台。これが無いと以下が動かない
    Dependabot alerts 脆弱性を検知して通知する
    Dependabot security updates 修正版へ上げるPRを自動で作る
    ポイント

    alerts だけだと「アラートは来るが誰も直さない」状態になりやすいです。security updates まで有効にして、修正案がPRの形で届くようにするのが実用的でした。人間がやるのは中身を見てマージするだけになります。

    あわせて .github/dependabot.yml という設定ファイルも置きました。これがあると、脆弱性の有無に関係なく定期的な更新PRも作られます。

    .github/dependabot.yml
    version: 2
    updates:
      - package-ecosystem: npm
        # Next.jsアプリはリポジトリ直下ではなく frontend/ にある
        directory: /frontend
        schedule:
          interval: weekly
        open-pull-requests-limit: 5
        groups:
          minor-and-patch:
            update-types: [minor, patch]
    つまずきやすい点

    directory: /frontend の指定が重要です。今回のリポジトリはルート直下にWordPressのファイルもあり、Next.jsは frontend/ の中にあります。ここを間違えると package.json が見つからず、Dependabotはエラーも出さずに何もしません。「設定したのに動かない」で悩む典型的なパターンです。

    やったこと2: 検知できない領域を自分で作る

    Dependabotは強力ですが、npmパッケージしか見ません。今回のサイトで実際に起きた問題を並べると、拾えるのは1つだけでした。

    実際に起きたこと Dependabotで拾えるか
    next に脆弱性 拾える
    バックアップファイルが誰でもダウンロード可能 拾えない
    SSL証明書がホスト名と不一致 拾えない
    CMSが落ちてサイトのデータが全部空 拾えない

    パッケージの脆弱性より、設定ミスや置き忘れのほうが実際には多かったわけです。そこで、残りをカバーする監視をGitHub Actionsで作りました。

    GitHub Actionsは、GitHubのサーバー上でコマンドを定期実行できる仕組みです。毎朝9時に動かし、問題があればSlackへ通知します。

    .github/workflows/security-audit.yml(抜粋)
    on:
      schedule:
        # 毎日 09:00 JST(cron は UTC なので 00:00 を指定)
        - cron: "0 0 * * *"
      workflow_dispatch:   # 手動実行もできるようにする

    チェックしているのは4領域です。

    1. npm依存パッケージ … npm audit の結果から high 以上を報告
    2. WordPress本体     … 導入バージョンと最新版を比較
    3. 公開ファイル       … 見えてはいけないURLを実際に叩いて確認
    4. 稼働とSSL証明書    … 表示・API応答・証明書の残り日数

    実際の失敗から作った監視項目

    この中に、教科書には出てこない項目をひとつ入れました。

    scripts/security-audit.mjs(抜粋)
    if (/まだ実績が登録されていません|まだ記事がありません/.test(html)) {
      report("high", "health", "サイトは表示されるがCMSのデータが取得できていない");
    }

    デプロイのときに、ビルドは成功しページも表示されるのに、データが1件も出ないという状態に数時間はまりました。HTTPステータスは200なので、普通の死活監視では検知できません。

    監視項目は、教科書からではなく自分が踏んだ失敗から作るほうが実用的になります。

    通知は異常時だけにする

    Slackへの通知は、問題があったときだけ送るようにしました。正常時は何も送りません。

    毎日「異常なし」が届くと、そのうち誰も読まなくなります。 通知は「届いたら必ず見る」状態を保つことが重要で、そのためには送る回数を絞る必要があります。

    初回実行でいきなり本物が出た

    作った監視を初めて動かしたところ、25件を検知しました。そして、その中に実害のある指摘が含まれていました。

    CRITICAL (exposure) /wp/readme.html が公開状態(200 / 7406バイト)

    readme.html はWordPressに最初から入っているファイルで、WordPressのバージョン番号が書かれています。

    これがなぜ問題かというと、攻撃する側にとって攻撃対象を絞り込むための情報だからです。「このサイトはWordPress 7.0.2だから、このバージョンに効く攻撃を試そう」と判断できてしまいます。

    削除すべきファイルなのですが、WordPressを更新するたびに復活します。だからこそ、一度消して終わりではなく定期チェックの対象にする価値があります。

    作った意味があった

    これはDependabotでは絶対に拾えません。パッケージの脆弱性ではなく、置き忘れだからです。移行のときの .wpress と同じ種類の問題が、まだ他にもあったということでした。

    誤報も出た(そして直した)

    同時に、誤検知も1件ありました。

    CRITICAL (exposure) /wp/wp-config.php が公開状態(200 / 0バイト)

    wp-config.php にはデータベースのパスワードが平文で書かれています。もし中身が読めるなら重大な問題です。

    ただ、よく見ると0バイトです。中身は漏れていません。PHPファイルはサーバー側で実行されてから結果が返るので、このファイルの場合は実行結果が空になります。アクセスできること自体は正常な挙動でした。

    私の判定ロジックが「ステータス200 = 公開されている」という単純な条件だったために起きた誤報です。本文まで読んでから判定するように直しました。

    scripts/security-audit.mjs(修正後)
    const res = await get(`${CMS}${path}`);
    if (res.status !== 200) continue;
    
    // 200が返っても中身が空なら「漏れていない」。
    // 例: wp-config.php はPHPとして実行され、出力は空になる。
    const body = await res.text();
    const bytes = new TextEncoder().encode(body).length;
    
    if (bytes === 0) {
      note(`${path} は200を返すが中身は空(問題なし)`);
      continue;
    }
    
    // ファイルの性質によって深刻度を変える
    const severity = /wp-config\.php|\.wpress$/.test(path) ? "critical" : "high";
    report(severity, "exposure", `${path} の中身が読める状態(${bytes}バイト)`);

    修正後、検知件数は 25件 → 23件 になりました。誤報1件と、削除した readme.html の1件が消えた形です。

    監視を作るときの原則

    監視は動かしてから調整するものです。最初から完璧を狙う必要はありません。ただし誤報を放置してはいけないのは重要で、誤報が続くと通知そのものが信用されなくなり、本物の警告まで無視されるようになります。

    更新PRの見分け方 ── セマンティックバージョニング

    Dependabotを有効にした直後、脆弱性51件を検知し、修正PRが8件作られました。

    これを全部そのままマージしてよいかというと、まったく違います。判断の基準になるのがセマンティックバージョニングという約束事です。

    16 . 3 . 0
    │    │   └── パッチ: バグ修正。動作は変わらない
    │    └────── マイナー: 機能追加。既存の動作は壊さない
    └─────────── メジャー: 破壊的変更あり。動かなくなる可能性がある

    この約束に従うと、左端の数字が変わらなければ、原則として壊れません。逆に左端が変わるものは、動作確認なしでマージしてはいけません。

    今回の8件を分類するとこうなりました。

    # 内容 種別 判断
    1 brace-expansion パッチ すぐマージ可
    2 js-yaml 4.3.0 → 4.3.1 パッチ すぐマージ可
    3 postcss 8.4.39 → 8.5.23 マイナー すぐマージ可
    4 minor-and-patch グループ マイナー すぐマージ可
    5 eslint-config-next 14 → 16 メジャー×2 #7とセット
    7 next 14.2.5 → 16.3.0 メジャー×2 #5とセット
    6 tailwindcss 3.4.4 → 4.3.3 メジャー 要注意
    8 graphql-request 6.1.0 → 7.4.0 メジャー 単独で検証

    セットで扱う必要があるもの

    eslint-config-next は Next.js とバージョンを揃える前提のパッケージです。片方だけ上げると噛み合いません。 #5 と #7 は必ず一緒にマージする必要があります。

    特に注意が要るもの

    Tailwind CSS の v3 → v4 は、設定の書き方が根本から変わります。これまで tailwind.config.ts に書いていた内容を、CSS側へ移す作業が必要です。

    怖いのは、ビルドが通っても見た目が崩れる可能性がある点です。「マージしたらエラーになる」なら気付けますが、「マージしたら静かにデザインが変わる」は気付きにくい。これは更新ではなく移行作業として、時間を取って扱うべきものです。

    なぜ一度に全部マージしないのか

    8件をまとめてマージすれば早く終わります。それをしなかった理由が2つあります。

    理由1: 問題が起きたとき原因が分からなくなる

    8件を同時に入れて表示が崩れたら、原因はどれでしょうか。切り分けるには結局1つずつ戻すことになり、余計に時間がかかります。

    変更は1つずつ、確認できる状態で入れる。 公開作業でSSL証明書の問題に数時間かけたのは、まさに「何が原因か切り分けられない状態」を作ってしまったからでした。

    理由2: マージするたびに他のPRが古くなる

    PRをマージすると package-lock.json というファイルが更新されます。すると残りのPRは古い状態のままになり、そのままではマージできません。

    Dependabotが自動で作り直してくれますが、少し時間がかかります。まとめられるものはまとめて処理するのが効率的です。

    今回の進め方

    安全な #1〜#4 を先に処理し、Dependabotが残りを作り直すのを待ってから、メジャーアップデートを1グループずつ扱う——という順序にしました。急がないものを急がないのも判断のうちです。

    確認項目チェックリスト

    今回の作業で使った確認項目をまとめておきます。次に同じことをするとき、これをなぞれば済むようにするためのものです。

    A. 依存パッケージを更新する前

    □ バージョン番号の左端が変わっていないか(メジャーか否か)
    □ セットで上げる必要のあるパッケージはないか
       (next と eslint-config-next のような組み合わせ)
    □ CI・プレビューのチェックが通っているか
    □ コンフリクトが出ていないか
    □ メジャーの場合、公式の移行ガイドを読んだか
    便利だった点

    VercelはPRごとにプレビュー環境を自動で作ります。本番と同じ環境でビルドが通るかを、マージ前に確認できました。「ビルドが通ることは確認済み」という状態でマージできるのは大きいです。

    B. マージした直後

    □ Vercelのデプロイが成功しているか
    □ トップページが表示されるか
    □ CMSのデータが実際に出ているか(空表示になっていないか)
    □ 主要ページが全部200を返すか
    □ 画像が表示されているか

    3つ目が特に重要です。ステータス200でも中身が空、という状態がありうることを、公開作業で学びました。ページが開けることと、中身が正しいことは別です。

    C. 定期的に確認すること(自動化済み)

    □ npm依存に high 以上の脆弱性がないか
    □ WordPress本体が最新か
    □ 公開してはいけないファイルが読めていないか
       ・移行用のバックアップ(.wpress)
       ・php.ini
       ・wp-config.php(中身が読める場合のみ)
       ・debug.log
       ・readme.html   ← WordPress更新のたび復活する
    □ ディレクトリ一覧が表示されていないか
    □ サイトが200を返すか
    □ ルートドメインが正しく転送されるか
    □ GraphQLがJSONを返すか
    □ SSL証明書の残り日数が14日を切っていないか

    証明書の閾値を14日にしたのには理由があります。Let’s Encryptは90日有効で、30日前から自動更新されます。つまり14日を切っている=自動更新が動いていないと判断できるわけです。

    D. 手作業でやること(自動化していない)

    □ WordPressプラグインの更新確認
    □ 管理者パスワードが十分に強いか
    □ 不要なプラグイン・テーマを削除したか
    □ ドメインの有効期限(切れるとサイト全体が止まる)
    □ 2要素認証の設定(GitHub / Vercel / サーバー)

    ドメインの有効期限は見落としやすい割に影響が大きい項目です。今回、作業中にたまたま管理画面で気付きました。

    今回の学び

    監視は既製品と自作を組み合わせる。 Dependabotだけでは今回の問題の4分の1しかカバーできませんでした。かといって全部自作するのも無駄です。既製品が得意な領域は任せ、残りを自分で埋めるのが現実的でした。

    監視項目は自分の失敗から作る。 「データが空なのに200を返す」という項目は、実際にはまったからこそ思いつきました。一般的な死活監視の教科書には出てきません。踏んだ失敗を監視項目に変換していくと、そのサイト固有の実用的な監視になっていきます。

    誤報は放置しない。 初回で誤報が1件出ました。誤報が続くと通知が信用されなくなり、本物の警告まで無視されるようになります。「オオカミ少年にしない」ことが、監視を維持する上でいちばん大事だと思っています。

    そして、急がないものを急がない。 8件のPRを前にすると全部片付けたくなりますが、メジャーアップデートは動作確認とセットです。急いで入れて壊すより、安全なものから順に入れて、残りは時間を取って扱う。作業を止める判断も作業のうちだと感じました。

    次回は、残っているメジャーアップデート(Next.js 16 と Tailwind CSS v4)に取り組む予定です。特にTailwindのv4は設定の書き方が変わるので、それなりの記事になりそうです。