Next.jsの/pages内でページ以外のコンポーネントを配置する方法

公開:2021/02/17
更新:2021/02/28
3 min読了の目安(約3500字TECH技術記事

結論

next.config.jspageExtensions オプションを設定すると良いです💪

next.config.js
module.exports = {
  // ページコンポーネントの拡張子を変更する
  // 以下の設定の場合、ページコンポーネントは pages/index.page.tsx などのようになる
  pageExtensions: ['page.tsx', 'page.ts']
}

上記の設定をしたら、ページコンポーネントの拡張子を変更する事を忘れないようにしてください!そうしないと、404 Not Foundになります。( API Routes も対象です! )

_app.tsx_document.tsx を指定している場合は、そちらも拡張子を変更する必要があります。この二つのファイルは、変更しなくてもエラーが出ないので注意してください!

具体的な解説

デフォルトの設定では、/pages内のコンポーネントは全てページを表示するためのコンポーネントとして扱われます。例えば以下のような感じ。

/pages
.
├── _app.tsx
├── header.tsx <- index.tsx内でしか使ってないコンポーネントのファイル
└── index.tsx <- ページコンポーネント
pages/index.tsx
import { Header } from "./header";

const HomePage = () => (
  <>
    {/* ... */}
    
    {/* ここで普通のコンポーネントとして使っている */}
    <Header />
    
    {/* ... */}
  </>
)

export default HomePage;
pages/header.tsx
/**
 * @description HomePageでしか使わないヘッダー
 * @note ページコンポーネントではない!
 **/
export const Header = () => (
  <header>
    {/* ... */}
  </header>
)

上記のソースコードを実行すると、header.tsx がページコンポーネントとして扱われてしまい、http://localhost:3000/header にアクセスすると 404 Not Found にならずにエラーが発生してしまいます。

これの回避策としては、header.tsx/pages から移動する事なんですが、問題なのは header.tsxindex.tsx でしか使われていないので、別の所に移動するとソースコードが分散してしまって、後から読むときにフォルダーを行ったり来たりしないと行けなくなります。

ソースコードが少なければそれで大丈夫ですが、大きなプロジェクトだとファイルを探すのも大変ですし、エディターの機能を使ってもファイルを見失う事がよくあるので、ただ /pages から移動するだけなのは、あまりやりたくない訳です。

なので、next.config.jspageExtensions オプションを設定して、ページコンポーネントの拡張子を変更する事によって、/pages内にページコンポーネント以外のコンポーネントを置けるようにします。

以下のように、next.config.jsを設定しましょう。

next.config.js
module.exports = {
  // ページコンポーネントの拡張子を変更する
  pageExtensions: ['page.tsx', 'page.ts']
}

上記のように設定したら、以下のようにファイル名を変更します。

/pages
.
├── _app.page.tsx  <- _app.tsxから変更
├── header.tsx     <- ページコンポーネントでは無いので、変更なし! 
└── index.page.tsx <- index.tsxから変更

上記の変更をして実行をすると、http://localhost:3000/header にアクセスしても 404 Not Found が表示されるようになります。

これによって、

  • ページコンポーネント -> .page.tsx
  • ページコンポーネント以外のコンポーネント -> .tsx

で、棲み分けることが出来るようになりました。

めでたしめでたし👏

高度な使い方

フォルダー構造のちょっとした知見をここで共有したいと思います🧭

先ずTypeScriptでは、index.tsxまたはindex.tsフォルダー名でインポートすることが出来ます。

.
├── example
|   └── index.ts
└── hoge.tsx
example/index.ts
export const message = "Example内のindex.tsファイルです"
hoge.tsx
// フォルダー名のみ指定しているが、example/index.tsの値をインポートできている
import { message } from "./example"; 

console.log( message ); // 出力結果 : Example内のindex.tsファイルです

上記の挙動を利用して、Next.jsの/pagesでもコンポーネントをフォルダーとして管理することが出来ます。

/pages
.
├── hoge
|   └── index.page.tsx
├── _app.page.tsx 
└── index.page.tsx

上記のフォルダー構造では、hoge/index.page.tsxhttp://localhost:3000/hoge のページとして扱われます。

ここまでの事を応用して、最終的に上記のインポートの挙動と今回のpageExtensionsオプションを組み合わせることで、もっと分かりやすいコンポーネント構造にすることが出来ます。以下はサンプルのフォルダー構造です👇

/pages
.
├── hoge
|   ├── components <- hoge内のみで使われているコンポーネントをまとめるフォルダー
|   |   ├── footer.tsx
|   |   └── header.tsx
|   └── index.page.tsx
|
├── _app.page.tsx 
|
└── index.page.tsx

上記のフォルダー構造にすることで、/pages/hoge内にコンポーネントをたくさん作っても、一目で依存関係が分かりやすくて管理しやすいですし、.module.cssファイルや、別のファイルをフォルダー内に入れる事も出来るので、コンポーネントの拡張性も上がっています。

StoryBookやテストのファイルなどが入れやすいので、私は良く多用しています👺

参考

この記事は、以下のissuesを参考にさせてもらっています👨‍💻

https://github.com/vercel/next.js/issues/8454

あとがき

ここまで読んでくれてありがとうございます🙏
記事に間違いなどがあれば、コメントなどで教えて頂けると嬉しいです。

これが誰かの参考になれば幸いです。
それではまた👋