RSC時代の静的解析:境界線をコードで強制する「防壁」の設計思想
Next.jsのApp Routerが登場して以来、我々エンジニアが直面している最大の敵は「ランタイムのブラックボックス化」だ。サーバーコンポーネント(RSC)でHooksを呼んでしまい、ブラウザで不意にクラッシュする。あるいは、意図せず重いライブラリをクライアント側にバンドルしてしまう。
これらを「ドキュメントの遵守」や「個人の注意」に委ねるのは、DevOpsの観点からは怠慢に等しい。システムで防げないエラーは、必ずどこかで顕在化する。今日は、ESLintを単なるリンターではなく、「アーキテクチャの強制執行エンジン」として昇華させる手法を解説する。
—
1. 境界防衛:`eslint-plugin-react-hooks` の限界を超えて
標準的な `react-hooks/rules-of-hooks` だけでは、RSCにおける「サーバー/クライアント境界」の違反は防げない。我々が構築すべきは、物理的に「サーバー側で実行不可能なコード」を静的解析レベルで拒絶する防壁だ。
構成の核となるカスタムルール
`@next/eslint-plugin-next` を活用するのは大前提だが、さらに一歩踏み込み、特定のディレクトリ配下では特定のAPI(`useState`や`useEffect`等)をコンパイル以前に「構文エラー」として弾く設定を導入する。
// .eslintrc.js
module.exports = {
overrides: [
{
// サーバーコンポーネントが配置されるディレクトリを特定
files: [‘src/app//page.tsx’, ‘src/app//layout.tsx’],
rules: {
// Hooksの利用を物理的に封殺する
‘react-hooks/rules-of-hooks’: ‘error’,
// サーバーコンポーネントでのクライアントサイドAPI使用を検知
‘no-restricted-imports’: [‘error’, {
paths: [{
name: ‘react’,
importNames: [‘useState’, ‘useEffect’, ‘useContext’],
message: “RSC内でHooksは利用できません。’use client’が必要です。”
}]
}]
}
}
]
};
—
2. パイプラインにおける「静的解析の最適化」ハック
CI/CDにおいて、ESLintはしばしばボトルネックになる。数万行のコードベースを毎回全走査するのは無駄の極みだ。ここで、「差分のみを解析する」アーキテクチャを導入する。
Git差分ベースの並列実行
単なる `eslint .` ではなく、`lint-staged` をCI環境へ拡張し、かつ `cache` オプションを極限まで活用する。
CI環境での実行コマンド設計
–cache: 変更のないファイルの解析結果をキャッシュ
–cache-strategy content: ファイルの内容ハッシュでキャッシュを判定
–parallel: CPUコアをフル活用して並列解析
eslint –ext .tsx,.ts . –cache –cache-strategy content –parallel –max-warnings 0
この設定により、CIの実行時間はリニアに縮小する。さらに、Docker環境では `node_modules/.cache` をビルドステージ間で共有(`–mount=type=cache`)することで、コールドスタート時を除き、数秒で解析を完了させることが可能になる。
—
3. 境界違反を「可視化」する独自CLIスクリプト
静的解析をすり抜けるような、より抽象度の高い境界違反(例:`use client`を忘れたファイルが、特定のプロトコル経由で呼び出される等)を監視するためには、AST(抽象構文樹)を直接叩く独自スクリプトが有効だ。
// scripts/check-rsc-boundaries.js
const fs = require(‘fs’);
const glob = require(‘glob’);
/
- ‘use client’が存在しないファイルで、特定の副作用関数を検知する
- 複雑なルールはESLintのプラグイン作成より、この手のスクリプトの方がメンテナンス性が高い
/
const files = glob.sync(‘src/components//.tsx’);
files.forEach(file => {
const content = fs.readFileSync(file, ‘utf-8’);
if (!content.includes(‘”use client”‘) && content.includes(‘useEffect’)) {
console.error(`[FATAL] Boundary Violation: ${file} に ‘use client’ がありません。`);
process.exit(1);
}
});
これをCIパイプラインの `pre-build` フェーズにフックさせる。これにより、ESLintの柔軟性と、カスタムスクリプトによる「絶対的な禁止事項」の二段構えで防壁を構築できる。
—
4. アーキテクトへの提言:なぜこれが必要か
我々が目指すべきは「エラーが出ないコード」ではなく、「エラーが出ないシステム(仕組み)」だ。
RSCは強力だが、その複雑性はエンジニアの認知負荷を激増させる。しかし、上記のような静的解析の強制と、パイプラインによる自動検知を組み込めば、開発者は「RSCであるか否か」という低レイヤの懸念から解放され、本来注力すべきビジネスロジックに集中できる。
「ツールにルールを教え込むな。ルールをツールの中に埋め込め。」
これが、大規模開発の荒波を乗り越えるための唯一の道だ。この設定を導入した瞬間、あなたのチームのデバッグコストは劇的に低下し、コードベースはより堅牢な「自己防衛するシステム」へと進化するだろう。さあ、今すぐパイプラインにこの楔を打ち込んでほしい。