Next.jsの開発中に発生する Warning: Prop `className` did not match. Server: Client: ...の解決策

Next.js の開発中に 発生する Warning: Prop className
did not match. Server: "vsc-initialized" Client: "" Error Component Stack の警告の解決策について紹介いたします。
“Prop className did not match” は、Video Speed Controller(VSC)というブラウザ拡張が <body>
へ自動付与する vsc-initialized
クラスが原因です。
SSR で生成された HTML にこのクラスが無い一方、クライアント側では拡張が先にクラスを追加するため水和(Hydration)時に差分が発生します。
まずは VSC が混入元であることを確認した上で、開発・運用フェーズ別に現実的な対処を行いましょう。
原因要素の特定
vsc-initialized
は Visual Studio Code ではなく Video Speed Controller 拡張が注入する識別子です。本番ユーザーが同拡張を入れていても水和前にクラスが追加されることは稀ですが、開発中はローカルサーバーの応答より拡張スクリプトの実行が速く、警告が顕在化します。
再現条件
# | 条件 | 説明 |
---|---|---|
1 | 開発コマンド (next dev ) 利用 | ローカルでの開発時に発生する |
2 | VS Code 拡張機能が有効 | body.classList.add('vsc-initialized') を実行 |
3 | <body> に Tailwind などのユーティリティクラスをバインド | Server → Client で文字列比較される |
エラー内容(全文)
javahook.js:608 Warning: Prop `className` did not match. Server: "vsc-initialized" Client: "" Error Component Stack
at body (<anonymous>)
at html (<anonymous>)
at RedirectErrorBoundary (redirect-boundary.js:73:9)
at RedirectBoundary (redirect-boundary.js:81:11)
at NotFoundErrorBoundary (not-found-boundary.js:76:9)
at NotFoundBoundary (not-found-boundary.js:84:11)
at DevRootNotFoundBoundary (dev-root-not-found-boundary.js:33:11)
at ReactDevOverlay (ReactDevOverlay.js:84:9)
at HotReload (hot-reloader-client.js:307:11)
at Router (app-router.js:181:11)
at ErrorBoundaryHandler (error-boundary.js:114:9)
at ErrorBoundary (error-boundary.js:161:11)
at AppRouter (app-router.js:536:13)
at ServerRoot (app-index.js:129:11)
at RSCComponent (<anonymous>)
at Root (app-index.js:145:11)
対策一覧
# | 手順 | 詳細 | 効果 |
---|---|---|---|
1 | VSC を無効化 | Chrome アドレスバー → chrome://extensions/ → Video Speed Controller を OFF | 根本的に警告を除去 |
2 | 開発専用プロファイル | 新規ブラウザプロファイルを作成し拡張をインストールしない | 拡張を残したまま安全に開発 |
3 | インライン同期スクリプト | _document.tsx の <body> 直後に下記を挿入 | 水和前にクラスを同期し警告を防止 |
インライン同期スクリプト の実装例(Next.js App Router)
tsx// app/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head />
<body className="bg-on-background">
{/* dev 環境のみクラス同期 */}
<script
dangerouslySetInnerHTML={{
__html: `(function(){
try{
if(location.hostname==='localhost' && !document.body.classList.contains('vsc-initialized')){
document.body.classList.add('vsc-initialized');
}
}catch(e){}
})();`,
}}
/>
<Main />
<NextScript />
</body>
</Html>
)
}
- インラインスクリプトは
<NextScript />
より前に置くことで React ハイドレーションより先に実行 されます。 localhost
判定で本番ビルドに影響しないよう限定します。- Tailwind 使用時にパージ対象外へしたい場合は
tailwind.config.js
にsafelist: ['vsc-initialized']
を追加すればスタイル損失を防げます。
本番環境向けチェックリスト
# | 確認項目 | コマンド/設定 | 目的 |
---|---|---|---|
1 | 本番ビルド検証 | yarn build && yarn start | 警告が出ないことを確認 |
2 | CSP ヘッダー | Content-Security-Policy: script-src 'self' | 拡張スクリプトの早期実行を抑制 |
3 | 早期クラス付与 | 先述の _document.tsx スクリプト | 拡張ありユーザーでも差分ゼロ |
まとめ
最適解は 開発ブラウザから Video Speed Controller を切り離す ことです。
どうしても無効化できない事情がある場合は、_document.tsx
内でサーバー・クライアントのクラスを同期させ、水和前の DOM 差分を解消してください。suppressHydrationWarning
は緊急避難策に過ぎませんので、恒久対応としては拡張の影響範囲を明確に隔離する設計を推奨いたします。
関連リンク
- article
Turbopack のインストールと初期セットアップ
- article
Next.js × Jotai で作る SSR 対応のモダン Web アプリケーション
- article
Next.js 14時代のESLint徹底解説:Server/Client ComponentsとApp Router対応の最適ルール設定
- article
Next.js での Zustand 活用法:App Router 時代のステート設計
- article
Next.jsの開発中に発生する Warning: Prop `className` did not match. Server: Client: ...の解決策
- article
Next.jsとEdge Runtimeを組み合わせて超爆速サーバーレス表示を実現する方法