Next.jsで「Module not found: Can’t resolve ‘fs’」エラー発生!解決策と根本原因をベテランが解説

Next.jsでの開発中、突然コンパイルエラー「Module not found: Can’t resolve ‘fs’」に遭遇して、思わず頭を抱えていませんか?「え、fsモジュールが使えないなんてことあるの?」と戸惑う気持ち、よくわかります。特にNode.jsに慣れているエンジニアほど、「なんでこれでエラーになるんだ?」と混乱しますよね。私も若かりし頃、同じエラーでずいぶんハマった経験がありますよ。

結論から言うと、このエラーの主な原因は、ブラウザ(クライアントサイド)では動作しないNode.jsの組み込みモジュールであるfsを、Next.jsがブラウザ向けにビルドしようとしたためです。解決策は、fsモジュールがサーバーサイドでのみ実行されるようにコードを修正するか、特定のWebpack設定を適用することになります。ご安心ください、一緒にこのやっかいなエラーを解決していきましょう!

1. エラーコード Next.js: Module not found: Can’t resolve ‘fs’ とは?(概要と緊急度)

まず、このエラーが何を意味しているのか、簡単におさらいしましょう。

  • fsモジュールとは?
    fsは「File System」の略で、Node.jsに組み込まれているモジュールの一つです。これを使うと、ファイルの読み書き、ディレクトリの作成・削除など、サーバー上のファイルシステムを直接操作できます。
  • なぜNext.jsでエラーになるのか?
    Next.jsは、サーバーサイドとクライアントサイド(ブラウザ)の両方で動作するアプリケーションを構築するためのフレームワークです。サーバーサイドではNode.js環境なのでfsは問題なく使えますが、クライアントサイドはブラウザ環境であり、セキュリティ上の理由からファイルシステムへの直接アクセスは許可されていません。 Next.jsがコードをビルドする際、クライアントサイドでfsモジュールが使われていると判断すると、「ブラウザではfsは解決できないよ!」とこのエラーを吐き出すのです。

緊急度:高

このエラーが発生すると、アプリケーションのビルドが失敗し、開発サーバーも起動できないため、開発が完全にストップしてしまいます。 早急な解決が求められる、緊急度の高いエラーです。

2. 最速の解決策 3選

それでは、具体的にどうすればこのエラーを解決できるのか、ベテランエンジニアが選んだ3つの方法をご紹介します。状況に応じて最適なものを選んでください。

解決策1: サーバーサイドでのみfsモジュールを使用する(最も推奨)

これが最も根本的で推奨される解決策です。fsモジュールはNode.jsの機能なので、Next.jsのサーバーサイドで実行される場所でのみ利用するようにコードを書き換えましょう。

  • API Routes内での利用:
    Next.jsのAPI RoutesはNode.jsのサーバーレス関数として動作するため、ここでfsモジュールを安全に利用できます。クライアントサイドからは、これらのAPIエンドポイントをフェッチしてデータを取得する形にします。
  • getServerSideProps, getStaticProps, getStaticPaths内での利用:
    これらのデータフェッチング関数は、ビルド時またはリクエスト時にサーバーサイドで実行されます。 したがって、これらの関数内でfsモジュールを使ってファイルを読み込み、その結果をプロップスとしてコンポーネントに渡すことができます。

具体的なコード例:

// pages/api/data.js (API Routeの例)
import fs from 'fs';
import path from 'path';

export default function handler(req, res) {
  const filePath = path.join(process.cwd(), 'data.json');
  try {
    const fileContents = fs.readFileSync(filePath, 'utf8');
    res.status(200).json(JSON.parse(fileContents));
  } catch (error) {
    res.status(500).json({ message: 'Error reading file', error: error.message });
  }
}
// pages/index.js (getServerSidePropsの例)
import fs from 'fs';
import path from 'path';

export async function getServerSideProps() {
  const filePath = path.join(process.cwd(), 'data.json');
  let data = {};
  try {
    const fileContents = fs.readFileSync(filePath, 'utf8');
    data = JSON.parse(fileContents);
  } catch (error) {
    console.error('Error reading data.json:', error);
  }

  return {
    props: {
      data,
    },
  };
}

function HomePage({ data }) {
  return (
    <div>
      <h1>ファイルから読み込んだデータ</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}
export default HomePage;

この方法は、Next.jsの設計思想に沿ったもので、コードの見通しも良くなり、長期的な保守性も向上します。 ほとんどの場合、これで解決できるはずです。

解決策2: ダイナミックインポートで実行環境を限定する

特定のコンポーネントやモジュールが「クライアントサイドでのみ動作し、サーバーサイドでのレンダリングは不要(または不可能)」な場合、Next.jsのダイナミックインポート機能を使って、サーバーサイドでの読み込みを回避することができます。

例えば、ブラウザ固有のAPIを使うライブラリが内部的にfsに似たNode.js専用の依存を持っている、といった稀なケースに有効です。

// components/BrowserOnlyComponent.js
// このコンポーネントはブラウザ環境でのみ動作し、
// 内部でたまたまNode.jsモジュールを参照するライブラリを使っていると仮定
import React, { useEffect, useState } from 'react';

function BrowserOnlyComponent() {
  const [content, setContent] = useState('');
  useEffect(() => {
    // ここでブラウザ固有の処理(例: IndexedDBの操作、Canvas APIなど)
    setContent("このコンテンツはクライアントサイドでのみレンダリングされています!");
  }, []);
  return <div>{content}</div>;
}

export default BrowserOnlyComponent;
// pages/index.js
import dynamic from 'next/dynamic';

const DynamicBrowserOnlyComponent = dynamic(
  () => import('../components/BrowserOnlyComponent'),
  { ssr: false } // ここがポイント!サーバーサイドでのレンダリングを無効化
);

function HomePage() {
  return (
    <div>
      <h1>ホームページ</h1>
      <p>これはサーバーサイドでレンダリングされた通常のコンテンツです。</p>
      <DynamicBrowserOnlyComponent /> {/* このコンポーネントはクライアントサイドでのみ表示 */}
    </div>
  );
}
export default HomePage;

注意点!

ssr: falseを使用すると、そのコンポーネントは初回ロード時にサーバーレンダリングされません。つまり、SEOや初期表示速度に影響を与える可能性があります。本当にそのコンポーネントがサーバーサイドレンダリング不要であることを確認してから使いましょう。あくまで「ブラウザでのみ動くべきコンポーネント」を分離する目的です。

解決策3: Webpackの設定でfsをmockする(非推奨)

これは最終手段に近い方法で、**「クライアントサイドのバンドルにfsモジュールを含めないように、Webpackに偽装させる」** 方法です。next.config.jsをカスタマイズしてWebpackの設定を変更します。

// next.config.js
module.exports = {
  webpack: (config, { isServer }) => {
    // サーバーサイドでない場合(=クライアントサイドの場合)のみ適用
    if (!isServer) {
      config.resolve.fallback = {
        fs: false, // fsモジュールを空のモジュールで置き換える
        path: false, // pathモジュールも同様に置き換える(fsとセットで使われることが多い)
        os: false, // 必要に応じて他のNode.jsモジュールも追加
      };
    }
    return config;
  },
};

この方法を使う前に熟考を!

この方法は、fsモジュールを必要とするコードを強制的に無効化してしまいます。その結果、本来動作すべき機能がサイレントに動作しなくなったり、予期せぬ別のエラーを引き起こす可能性があります。「本当にクライアントサイドでfsが必要ない」と確信できる場合、そして他に解決策がない場合の最後の手段として検討してください。多くの場合、解決策1か2で対処可能です。

3. エラーの根本原因と再発防止策

一時的にエラーを解決しても、根本原因を理解しなければ再発してしまいます。このエラーがなぜ起きるのか、そしてどうすれば今後同じミスをしないか、ベテランエンジニアとしての経験からお話しします。

根本原因: 「実行環境の違い」の認識不足

このエラーの核心は、Next.jsが提供するサーバーサイドレンダリング(SSR)や静的サイト生成(SSG)の強力な機能と、クライアントサイドでのJavaScript実行環境(ブラウザ)の明確な違いを十分に認識していなかったことにあります。

  • Node.js環境(サーバーサイド): ファイルシステムへのアクセス、ネットワークリクエスト、データベース接続など、OSレベルの操作が可能。
  • ブラウザ環境(クライアントサイド): セキュリティ上の理由から、ファイルシステムへの直接アクセスは不可能。DOM操作、Web API(Fetch APIなど)、ブラウザストレージの利用が主。

Next.jsはこれら両方の環境でコードが実行される可能性を考慮してビルドを行います。そのため、本来ブラウザで動かないfsがクライアントサイドで参照されるとエラーになるのです。

再発防止策: 意識の切り替えと役割分担の徹底

今後このエラーを発生させないためには、以下の点を意識しましょう。

  • 「ブラウザで動くコード」と「サーバーで動くコード」を明確に区別する意識を持つ:
    特にNode.jsでバックエンド開発の経験がある方は、無意識にfsなどのNode.jsモジュールを使ってしまいがちです。Next.jsにおいては、常に「このコードはどちらの環境で実行されるのか?」を自問自答する習慣をつけましょう。
  • Next.jsのデータフェッチング機能を活用した役割分担:
    • データ取得・ファイル操作: fsが必要な場合は、Next.jsのAPI RoutesgetServerSideProps, getStaticProps, getStaticPaths内で完結させましょう。これらの関数がサーバーサイドでデータを処理し、その結果をクライアントサイドのコンポーネントに渡すのが理想的な流れです。
    • UI表示・ユーザーインタラクション: クライアントコンポーネントでは、UIのレンダリングやユーザーからの入力処理に集中し、必要なデータはAPI経由で取得するように徹底します。
  • サードパーティライブラリの確認:
    新たに導入するライブラリが、内部的にNode.js専用の機能(fsなど)を使用していないか、ドキュメントを軽く確認する習慣をつけましょう。もし使っている場合は、そのライブラリがNext.jsのサーバーサイドでしか使えないことを理解しておくか、代替手段を検討してください。

この「実行環境の違い」を深く理解し、適切な場所で適切なツールを使うという考え方を徹底することで、開発効率が飛躍的に向上し、fsに限らず、同様の「Module not found」エラーで悩むことは格段に減るはずです。

4. まとめ

Next.js開発で遭遇する「Module not found: Can’t resolve ‘fs’」エラーは、多くの開発者が一度は通る道、いわば「通過儀礼」のようなものです。

このエラーは、主にクライアントサイドでNode.jsのファイルシステムモジュールfsを参照しようとしたときに発生します。 解決策としては、以下の3つが有効でした。

  • 最も推奨: fsモジュールをNext.jsのサーバーサイド機能(API Routes、getServerSidePropsなど)内で使用する。
  • 次点: next/dynamicssr: falseオプションで、特定のコンポーネントのサーバーサイドレンダリングを無効にする。
  • 最終手段: next.config.jsでWebpackの設定をカスタマイズし、fsモジュールをmockする(非推奨)。

このエラーは、Next.jsのサーバーサイドとクライアントサイドという二つの異なる実行環境の理解を深める良い機会でもあります。焦らず、まずは原因となっている箇所を特定し、コードの実行環境を意識しながら修正してみてください。

今回の知識が、あなたのNext.js開発の一助となれば幸いです。もし他に困ったことがあれば、いつでも気軽に相談してくださいね!

“`