pnpmとNext.jsでのReact Suspense型エラーを解決する
最近 Next.js プロジェクトでpnpmを使用中に、React Suspenseコンポーネントに関する不思議な型エラーに遭遇しました。この記事では、その問題と解決策を共有します。
発生した問題
Next.jsプロジェクトでReactのSuspenseコンポーネントを使用した際に、以下のようなTypeScriptエラーが発生しました:
Type 'ReactElement<unknown, string | JSXElementConstructor<any>>' is not assignable to type 'ReactNode'.
Property 'children' is missing in type 'ReactElement<unknown, string | JSXElementConstructor<any>>' but required in type 'ReactPortal'.
コードは以下のような形でした:
<Suspense
fallback={
<div className="...">
{/* スケルトンローディングUI */}
</div>
}>
<MainComponent />
</Suspense>
一見すると問題ないように見えますが、ビルド時にエラーが発生し、ビルドが失敗していました。
原因の特定
最初は以下のような対処法を試しました:
- JSXフラグメント(
<>...</>
)でfallbackの内容をラップする -
as React.ReactNode
で型付けを明示する - fallbackの内容を変数に抽出する
しかし、いずれの方法でも問題は解決しませんでした。
調査を進めると、この問題はpnpmの依存関係管理の特性と関連していることがわかりました。pnpmは「非フラット」な依存関係ツリーを作成し、各パッケージが自身の依存関係を独自にインストールします。これにより、プロジェクト内で異なるパッケージが異なるバージョンの@types/react
を参照する状況が発生していました。
解決策:tsconfig.jsonにpathsを追加
最終的に、tsconfig.json
に以下の設定を追加することで問題が解決しました:
{
"compilerOptions": {
// 他の設定...
"paths": {
"react": ["./node_modules/@types/react"]
}
}
}
この設定により、TypeScriptコンパイラに「reactモジュールの型定義はプロジェクトルートのnode_modules/@types/react
から取得してください」と明示的に指示することになります。これでSuspenseコンポーネントの型定義が正しく解決され、エラーが解消されました。
なぜこれで解決するのか?
pnpmを使用する際、このような型定義の不一致が発生する主な理由は:
- 依存関係の厳格な分離: pnpmは各パッケージの依存関係を分離して管理します
- 複数のReactバージョン: プロジェクト内で異なるReactの型定義が混在する可能性があります
- 型定義の継承チェーン: 複雑なプロジェクトでは、型定義の継承チェーンが正しく解決されないことがあります
paths
を設定することで、TypeScriptに型定義を探す場所を明示的に指示し、一貫した型定義を使用するようになります。
pnpmでの他の対処法
同様の問題に遭遇した場合、以下の対策も効果的です:
-
.npmrc
ファイルにshamefully-hoist=true
を設定するshamefully-hoist=true
これにより依存関係がよりフラットに配置されますが、pnpmの利点である厳格な依存関係分離が一部失われます。
-
pnpm.overrides
を使って特定のパッケージバージョンを強制する// package.json "pnpm": { "overrides": { "@types/react": "^18.2.0" } }
まとめ
pnpmの優れた依存関係管理は多くのメリットがありますが、時にTypeScriptの型定義に関する問題を引き起こすことがあります。特にReactのようなコアライブラリの型定義が関わる場合、tsconfig.json
のpaths
設定で型定義の参照先を明示することで解決できることがあります。
この記事が同様の問題に直面している方の助けになれば幸いです。また、他にも効果的な解決策をご存知の方は、ぜひコメントで共有してください!
Discussion