Next.jsプロジェクトで、あるファイルからfsモジュールをimportしたところ「Module not found: Can't resolve 'fs'」エラーが発生しました。以下のうち、このエラーが発生しない書き方はどれですか?
解説
正解は「getStaticProps内でfsを使う」方法です。getStaticPropsはNext.jsがビルド時にサーバー側でのみ実行する関数で、この中のコードはクライアント用バンドルに含まれません。そのためwebpackがfsをブラウザ向けに解決しようとする問題が起きず、エラーになりません。他の3つはいずれもクライアントバンドルにfsが含まれる(含まれうる)ため失敗します。「バンドル対象かどうか」がすべてを決めるこのエラーの本質は、webpackがブラウザ用バンドルを生成する段階でfsを解決できないという点にあります。実行時にサーバーかブラウザかを判定しても意味がありません。webpackはコードを静的に解析してimport/requireを辿るため、ランタイム分岐の中にあろうと、importが構文上存在すればバンドル対象になります。たとえば次のようなコードは一見サーバーでしか動かないように見えますが、エラーになります。import fs from 'fs'; if (typeof window === 'undefined') { const data = fs.readFileSync('./data.json', 'utf-8'); }webpackはif文の中身まで条件評価せず、先頭のimport fsを見た時点でバンドルに含めようとします。dynamic import(await import('fs'))も同様で、そのコードがクライアントコンポーネントに属していればバンドル対象です。useEffect内での呼び出しもランタイムの話であり、バンドル段階では区別されません。Next.jsで「サーバー専用コード」として扱われる場所getServerSideProps / getStaticProps: Pages Routerにおけるデータ取得関数。これらはサーバー専用としてツリーシェイクされ、クライアントバンドルから除外されるAPI Routes(pages/api/配下): 完全にサーバーで実行されるApp RouterのServer Component(デフォルト): 'use client'を宣言しなければサーバーでのみ実行される実務では、fsを使うロジックをこれらの場所に閉じ込め、読み取った結果だけをpropsやレスポンスとしてクライアントに渡す設計が基本です。サーバー専用モジュールを誤ってクライアントに持ち込まないよう、server-onlyパッケージをimportしておくと、万が一クライアントバンドルに含まれた時点でビルドエラーにしてくれるため安全策として有効です。