Next.jsとは何か?メリットや使い方を分かりやすく解説
はじめに:現代Web開発の進化とフレームワークの必要性
Web開発の世界は常に進化しています。かつては静的なHTMLページが主流でしたが、JavaScriptの登場により、ブラウザ上で動的にコンテンツが変化する、いわゆる「リッチなユーザー体験」が実現できるようになりました。これにより、GmailやGoogle Mapsのような、まるでデスクトップアプリケーションのように操作できるWebサイト、すなわちシングルページアプリケーション(SPA)が登場しました。
SPAは、ページ遷移時に画面全体をリロードする必要がないため、非常にスムーズな操作感を提供します。しかし、SPAにはいくつかの課題も存在しました。
- 初期表示速度の遅さ: 最初のHTMLには最低限の情報しか含まれておらず、JavaScriptのダウンロードと実行が完了するまでコンテンツが表示されないため、ユーザーは長い間白い画面を見せられることになります。
- SEO(検索エンジン最適化)の課題: 多くの検索エンジンのクローラーは、JavaScriptを実行する能力に限界があるため、JavaScriptによって後から描画されるコンテンツを適切に認識できない可能性がありました。
- 開発の複雑さ: SPAはクライアントサイドのJavaScriptにロジックが集中するため、状態管理やルーティングなど、複雑な開発が必要になります。
これらの課題を解決し、より効率的でパフォーマンスの高いWebアプリケーション開発を実現するために、様々なツールやライブラリ、そしてフレームワークが登場しました。その中でも、特にReactを用いたアプリケーション開発において、これらの課題をモダンなアプローチで解決し、高い生産性とパフォーマンス、そして優れた開発体験を提供するフレームワークとして急速に普及したのが、Next.jsです。
この記事では、Next.jsが一体どのようなもので、なぜこれほどまでに多くの開発者や企業に選ばれているのか、そのメリットや基本的な使い方、そして少し進んだ活用方法までを、初心者にも分かりやすく、かつ詳細に解説していきます。
この記事を読めば、Next.jsがあなたのWeb開発にもたらす可能性を理解し、実際に使い始めるための第一歩を踏み出せるようになるでしょう。
Next.jsとは?Reactアプリケーション開発のためのフルスタックフレームワーク
端的に言うと、Next.jsはReactのために作られた、オープンソースのWebアプリケーションフレームワークです。Facebook(現Meta)が開発したJavaScriptライブラリであるReactは、UI(ユーザーインターフェース)を構築するための強力なツールですが、それ単体ではSPAを効率的に開発するための機能(ルーティング、サーバーサイドレンダリングなど)は提供していません。これらの機能は、React RouterやReduxといった追加のライブラリと組み合わせて構築するのが一般的でした。
しかし、複数のライブラリを組み合わせるアプローチは、設定が複雑になったり、ライブラリ間の連携で問題が発生したりする可能性がありました。また、前述のSPAの課題(初期表示速度、SEO)を解決するためには、サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)といった技術を導入する必要がありますが、これらをReact単体でゼロから実装するのは非常に手間がかかります。
Next.jsは、これらの問題を解決するために登場しました。Reactアプリケーション開発に必要な多くの機能(ルーティング、SSR/SSG、APIルート、コード分割など)をフレームワークとしてビルドインで提供しています。これにより、開発者は複雑な設定に時間を費やすことなく、アプリケーションの本質的な開発に集中できるようになります。
Next.jsは、Vercelという企業によって開発・メンテナンスされています。VercelはNext.jsに最適化されたホスティングプラットフォームも提供しており、開発からデプロイまでを非常にスムーズに行うことができます。
Next.jsの主な特徴をいくつか見てみましょう。
- ファイルシステムベースのルーティング: プロジェクト内の特定のディレクトリ構造に基づいて自動的にルーティングが設定されます。
- 多様なレンダリング戦略: SSR、SSG、ISR(Incremental Static Regeneration)、CSR(Client-Side Rendering)など、ページの特性に応じて最適なレンダリング方法を選択できます。
- APIルート: Node.jsサーバーコードをプロジェクト内に簡単に記述し、APIエンドポイントとして機能させることができます。
- コード分割(Code Splitting): ページごとに必要なJavaScriptだけを読み込むように自動的に最適化されます。
- Hot Module Replacement (HMR) / Fast Refresh: 開発中にコードを変更した際に、アプリケーションを再読み込みすることなく変更が即座に反映されます。
- TypeScript、CSS Modules、Styled JSXなどのサポート: モダンなWeb開発でよく使われる技術が最初からサポートされています。
- 画像の最適化 (
next/image
): 画像をデバイスやブラウザに合わせて最適な形式・サイズで提供し、パフォーマンスを向上させます。 - フォントの最適化 (
next/font
): Webフォントの読み込みを最適化し、レイアウトシフト(CLS)を防ぎます。
これらの特徴が組み合わさることで、Next.jsはReact開発における様々な課題を解決し、より高性能で開発効率の高いWebアプリケーション開発を可能にしているのです。
Next.jsのメリット:なぜNext.jsを選ぶのか?
Next.jsがこれほど人気を集めているのには明確な理由があります。ここでは、Next.jsを採用する主なメリットを詳細に見ていきましょう。
1. 開発体験(Developer Experience: DX)の向上
Next.jsは開発者が快適に効率よく開発を進められるよう、様々な機能を提供しています。
- 設定不要のルーティング(Pages Router / App Router):
- Pages Router: プロジェクトのルートにある
pages
ディレクトリ内にReactコンポーネントファイルを作成するだけで、そのファイル名に応じたURLパスでページが表示されるようになります。例えば、pages/about.js
を作成すれば/about
でアクセスできるようになります。これは直感的で非常に簡単です。動的なルーティングも、pages/posts/[id].js
のようにファイル名に角括弧を使うことで容易に実現できます。 - App Router: Next.js 13から導入された新しいルーティングシステムです。
app
ディレクトリを使用し、各ルートセグメントに対応するディレクトリ内にpage.js
ファイルを配置します。こちらもファイルシステムベースですが、レイアウト (layout.js
) やローディングUI (loading.js
)、エラーハンドリング (error.js
) などをディレクトリ構造で管理できるため、より整理されたコード構造を実現できます。どちらのルーターも、開発者がルーティングライブラリの設定に頭を悩ませる必要がなくなります。
- Pages Router: プロジェクトのルートにある
- 高速なホットリロードとFast Refresh:
- コードを変更すると、ブラウザの表示が瞬時に更新されます。特にReactコンポーネントの状態を維持したまま更新できる「Fast Refresh」は、開発中の手戻りを減らし、UIの調整などをスムーズに行える強力な機能です。
- ビルトインのTypeScriptサポート:
- TypeScriptプロジェクトとして簡単にセットアップでき、型安全な開発をサポートします。大規模なアプリケーション開発においては、型の恩恵は非常に大きいです。
- APIルートによるバックエンド連携の容易さ:
pages/api
またはapp/api
ディレクトリ内にファイルを作成するだけで、サーバーレス関数として動作するAPIエンドポイントを簡単に作成できます。これにより、データのフェッチやフォームの送信処理など、クライアントサイドだけでは完結できない処理をNext.jsプロジェクト内で一元的に管理できます。フロントエンドとバックエンドを同じプロジェクト内で開発できるため、開発効率が向上します。
- CSS ModulesとStyled JSXのサポート:
- CSS Modulesは、クラス名のスコープをファイル内に限定することで、スタイルの衝突を防ぎます。Next.jsはCSS Modulesを特別な設定なしにサポートしています。また、Styled JSXは、コンポーネント内にCSSを記述できるライブラリで、これもデフォルトで利用可能です。その他、Sassや様々なCSS-in-JSライブラリ(styled-components, Emotionなど)も簡単に導入できます。
これらの機能により、開発者はアプリケーションのビジネスロジックやUIの構築に集中でき、開発のスピードと楽しさが向上します。
2. パフォーマンスの向上
Next.jsは、Webサイトやアプリケーションのパフォーマンスを最大化するための様々な最適化手法をデフォルトで提供しています。
- サーバーサイドレンダリング (SSR) と静的サイト生成 (SSG):
- SPAの課題であった初期表示速度とSEOの問題を根本的に解決します。
- SSR: ユーザーからのリクエストがあるたびに、サーバー側でReactコンポーネントをHTMLにレンダリングし、そのHTMLをクライアントに送信します。これにより、ブラウザはレンダリング済みのHTMLを受け取るため、すぐにコンテンツを表示できます。動的なコンテンツやユーザー固有のコンテンツを表示するページに適しています。
- SSG: ビルド時にあらかじめすべてのページをHTMLファイルとして生成しておきます。ユーザーからのリクエストがあった際は、生成済みのHTMLファイルをCDN(Content Delivery Network)から配信するため、非常に高速な表示が可能です。コンテンツがあまり頻繁に更新されないページ(ブログ記事、製品紹介ページなど)に適しています。
- Next.jsでは、Pages Routerの場合は
getServerSideProps
またはgetStaticProps
といったデータ取得関数、App Routerの場合はサーバーコンポーネントでの非同期データ取得やfetch
APIのキャッシュ設定によって、これらのレンダリング戦略を簡単に選択・実装できます。
- 増分静的再生成 (ISR):
- SSGはビルド時にすべてのページを生成するため、コンテンツが頻繁に更新される場合はビルドのたびにサイト全体を再デプロイする必要があり、非効率です。ISRは、SSGで生成したページを、一定時間経過後や特定のリクエストをトリガーに、バックグラウンドで再生成する仕組みです。これにより、静的なページの高速性を維持しつつ、コンテンツの更新を効率的に反映できます。Pages Routerでは
getStaticProps
のrevalidate
オプション、App Routerではfetch
APIのキャッシュ設定やセグメントごとのrevalidate
オプションで実現できます。
- SSGはビルド時にすべてのページを生成するため、コンテンツが頻繁に更新される場合はビルドのたびにサイト全体を再デプロイする必要があり、非効率です。ISRは、SSGで生成したページを、一定時間経過後や特定のリクエストをトリガーに、バックグラウンドで再生成する仕組みです。これにより、静的なページの高速性を維持しつつ、コンテンツの更新を効率的に反映できます。Pages Routerでは
- コード分割(Code Splitting)の自動化:
- Next.jsは、ページごとに必要なJavaScriptコードだけを読み込むように自動的に最適化を行います。これにより、初回ロード時のJavaScriptファイルサイズが小さくなり、ページの表示速度が向上します。
- 画像の最適化 (
next/image
コンポーネント):- Webサイトのパフォーマンスにおいて、画像はボトルネックになりやすい要素です。
next/image
コンポーネントは、画像の遅延読み込み(Lazy Loading)、デバイスサイズやビューポートに応じた最適なサイズの提供、WebPなどのモダンな画像フォーマットへの自動変換などを自動で行います。これにより、画像の表示パフォーマンスを大幅に向上させることができます。
- Webサイトのパフォーマンスにおいて、画像はボトルネックになりやすい要素です。
- フォントの最適化 (
next/font
):- Webフォントの読み込みは、ページのレンダリングをブロックしたり、フォントの読み込み前と後でテキストの表示が変わることによるレイアウトシフト(CLS)を引き起こしたりする可能性があります。
next/font
は、フォントファイルをローカルにダウンロードしてホストしたり、自動的にサブセット化したりすることで、これらの問題を解決し、フォント表示を最適化します。
- Webフォントの読み込みは、ページのレンダリングをブロックしたり、フォントの読み込み前と後でテキストの表示が変わることによるレイアウトシフト(CLS)を引き起こしたりする可能性があります。
これらのパフォーマンス最適化機能は、開発者が意識的に設定しなくても、Next.jsの機能として提供されるため、簡単に高性能なWebサイトを構築できます。
3. SEOフレンドリー
SPAの課題であったSEOも、Next.jsのレンダリング戦略によって解決されます。
- SSRとSSGによるクローラーの認識向上:
- SSRやSSGによって、サーバー側であらかじめHTMLが生成されるため、検索エンジンのクローラーはJavaScriptを実行することなく、ページのコンテンツを適切に読み取ることができます。これにより、検索エンジンのランキングにおいて有利になります。
- Head要素の管理 (
next/head
):- ページのタイトルやmeta description、OGPタグといったSEOやSNSシェアにおいて重要な
head
要素内の情報を、コンポーネント内で簡単に管理・設定できます。Pages Routerではnext/head
コンポーネント、App Routerではレイアウトファイルなどで簡単に設定できます。
- ページのタイトルやmeta description、OGPタグといったSEOやSNSシェアにおいて重要な
4. 運用・保守の容易さ
Next.jsはフレームワークとして多くの機能を提供しているため、プロジェクトの構成要素が統一されやすく、運用や保守が容易になります。
- フルスタックフレームワークとしての統一性:
- フロントエンドのレンダリングからバックエンドのAPIまでを同じNext.jsプロジェクト内で管理できるため、プロジェクト全体の構造がシンプルになり、開発者間の連携やコードの共有が容易になります。
- Vercelなどのプラットフォームとの親和性:
- Next.jsはVercelというホスティングプラットフォームと密接に連携しています。Next.jsプロジェクトをVercelにデプロイするのは非常に簡単で、Gitリポジトリと連携させるだけで、ビルドやデプロイ、プレビュー環境の構築などが自動で行われます。また、VercelはNext.jsのISRなどの機能を最大限に活用できるよう最適化されています。
5. スケーラビリティ
Next.jsは、比較的小規模なWebサイトから大規模なWebアプリケーションまで、幅広い規模のプロジェクトに対応できます。
- サーバーレス関数としてのAPIルート:
- APIルートは、Vercelなどのプラットフォームではサーバーレス関数としてデプロイされます。サーバーレス関数は、トラフィックに応じて自動的にスケールするため、急なアクセス増加にも対応しやすくなります。
- 多様なレンダリング戦略の組み合わせ:
- プロジェクト内でSSR、SSG、ISR、CSRといった複数のレンダリング戦略を組み合わせて利用できます。これにより、ページの特性に応じて最適な戦略を選択し、全体として高いパフォーマンスとスケーラビリティを持つアプリケーションを構築できます。
これらのメリットを総合すると、Next.jsは現代のWeb開発において、高い生産性、優れたパフォーマンス、強力なSEO対策、そして容易な運用保守を実現するための非常に有効な選択肢と言えるでしょう。
Next.jsのデメリット(あるいは考慮事項)
どんな技術にもメリットがあればデメリット、あるいは利用する上で考慮すべき点が存在します。Next.jsについても同様です。
- 学習コスト:
- Next.jsはReactを基盤としているため、Reactの基本的な知識は前提となります。さらに、Next.js独自の概念(ファイルシステムベースルーティング、データ取得方法、レンダリング戦略など)を学ぶ必要があります。特に、Pages RouterからApp Routerへの移行期にある現在、両方の概念を理解する必要がある場合があります。React単体での開発に慣れている開発者にとっては、フレームワークに乗っかるための学習コストがかかります。
- Vercel以外のデプロイ先での設定:
- Next.jsはVercelによって開発されているため、Vercelでのデプロイが最もスムーズで簡単です。しかし、AWS Amplify, Netlify, Heroku, Azure App Service, Dockerなど、他のプラットフォームにデプロイすることも可能です。ただし、その場合は、SSRやISRなどのNext.jsの機能を最大限に活用するための設定や環境構築がVercelほど簡単ではない場合があります。特に、SSRを実行するためのNode.jsサーバー環境の構築や、ISRのための再検証(revalidation)の仕組みの実装などが必要になることがあります。
- フレームワーク固有の制約:
- Next.jsは特定のアーキテクチャや開発パラダイム(ファイルシステムベースルーティング、特定のデータ取得関数など)に基づいて設計されています。これにより開発効率が向上しますが、そのフレームワークの設計思想から大きく外れるような独特の要件を持つアプリケーションを開発する場合、かえって開発が難しくなる可能性もゼロではありません。例えば、非常に大規模で複雑なクライアントサイドの状態管理が中心となる、純粋なSPAライクなアプリケーションを構築する場合、Next.jsのサーバーサイドの機能が必ずしも必要でなくなり、かえってPages Routerのクライアントサイドナビゲーションの挙動やApp Routerでのクライアントコンポーネントの扱いなどが、React単体や別のSPAフレームワーク(Create React App、Viteなど)とは異なる挙動を示すことに注意が必要になる場合があります。
- ビルド時間の増大:
- 特にSSGを利用して多数のページを生成する場合や、ISRで多くのページを再検証する設定にしている場合、プロジェクトの規模が大きくなるにつれてビルド時間が長くなる傾向があります。これは、ビルドプロセスが複雑になるためです。ただし、Next.jsやVercelはビルドの最適化にも力を入れており、ビルドキャッシュなどの機能によってこの問題の軽減を図っています。
これらの考慮事項はありますが、多くのWebアプリケーション開発においては、Next.jsの提供するメリットがデメリットを上回ることが多く、その採用が推奨される理由となっています。重要なのは、プロジェクトの要件やチームのスキルセットを考慮して、Next.jsが最適な選択肢であるかどうかを判断することです。
Next.jsの基本的な使い方
それでは、実際にNext.jsプロジェクトを始めてみるための基本的な使い方を見ていきましょう。
1. 環境構築
Next.jsを使い始めるには、まずNode.jsがインストールされている必要があります。Node.jsの公式サイトからダウンロードしてインストールしてください。バージョン18.17以降を推奨します。
インストールが完了したら、ターミナルやコマンドプロンプトを開き、以下のコマンドを実行して新しいNext.jsプロジェクトを作成します。
“`bash
npx create-next-app@latest my-next-app
または npm を使用する場合:
npm init next-app@latest my-next-app
または yarn を使用する場合:
yarn create next-app@latest my-next-app
または pnpm を使用する場合:
pnpm create next-app@latest my-next-app
“`
my-next-app
の部分は好きなプロジェクト名に変更してください。
このコマンドを実行すると、いくつかの質問が表示されます。プロジェクト名の他に、TypeScriptを使用するか、ESLintを使用するか、Tailwind CSSを使用するか、src/
ディレクトリを使用するか、App Routerを使用するか、import aliasを設定するかなどを尋ねられます。今回は基本的な例として、以下のように選択してみましょう(App RouterをYesとして進めます)。
What is your project named? `my-next-app`
Would you like to use TypeScript? `Yes`
Would you like to use ESLint? `Yes`
Would you like to use Tailwind CSS? `No`
Would you like to use `src/` directory? `No`
Would you like to use App Router? (recommended) `Yes`
Would you like to customize the default import alias (@/*)? `No`
質問に答え終わると、Next.jsのプロジェクトが自動的に作成され、必要なパッケージがインストールされます。
プロジェクトフォルダに移動し、開発サーバーを起動します。
“`bash
cd my-next-app
npm run dev
または yarn dev
または pnpm dev
“`
サーバーが起動したら、ブラウザで http://localhost:3000
にアクセスしてみてください。Next.jsのウェルカムページが表示されるはずです。
これで、基本的なNext.jsプロジェクトの開発環境が整いました。
2. プロジェクト構造(App Router)
create-next-app
でApp Routerを選択した場合の基本的なプロジェクト構造を見てみましょう。
my-next-app/
├── app/ # App Routerのルートディレクトリ
│ ├── layout.tsx # ルートレイアウト (HTML, bodyなど)
│ ├── page.tsx # トップページ
│ └── globals.css # グローバルスタイル
├── public/ # 静的ファイル (画像など) を置くディレクトリ
│ └── ...
├── .eslintrc.json # ESLintの設定ファイル
├── .gitignore # Gitで無視するファイルを指定
├── next.config.js # Next.jsの設定ファイル
├── package.json # プロジェクトの依存関係やスクリプトを定義
├── pnpm-lock.yaml # (pnpmを使用した場合)
├── README.md
├── tsconfig.json # TypeScriptの設定ファイル
└── ...
app/
: App Routerを使用する場合のメインディレクトリです。このディレクトリ内にファイルやフォルダを作成することで、ルーティングやUI構造を定義します。app/layout.tsx
: アプリケーション全体のルートレイアウトを定義するファイルです。html
タグやbody
タグ、メタデータなどを記述します。このファイルは必須であり、他のレイアウトやページはこのルートレイアウトの子となります。app/page.tsx
: 各ルーティングセグメントにおけるページのコンポーネントを定義するファイルです。例えば、app/page.tsx
はルートURL (/
) に対応するトップページ、app/about/page.tsx
は/about
に対応するページとなります。app/globals.css
: アプリケーション全体に適用されるグローバルなCSSスタイルを記述します。public/
: 静的なファイル(画像、フォントなど)を置くディレクトリです。このディレクトリ内のファイルは、ルートパス (/
) から直接アクセスできます。例えば、public/my-image.png
は/my-image.png
としてアクセスできます。next.config.js
: Next.jsのビルド設定や実行時の設定を行うためのファイルです。環境変数、ヘッダー、リダイレクト、画像最適化の設定などを記述します。package.json
: プロジェクト名、バージョン、依存関係、開発スクリプトなどを定義します。開発サーバー起動 (npm run dev
) やビルド (npm run build
)、起動 (npm run start
) などのコマンドがここに定義されています。
3. ルーティング(App Router)
App Routerでは、app
ディレクトリ内のフォルダ構造と特定のファイル名によってルーティングが定義されます。
- ルートファイル
page.tsx
: 各ディレクトリ(ルーティングセグメント)のルートページを定義します。
app/
├── page.tsx # /
└── about/
└── page.tsx # /about - ネストされたルーティング: ディレクトリをネストすることで、URLのパス構造に対応します。
app/
├── dashboard/
│ ├── page.tsx # /dashboard
│ └── settings/
│ └── page.tsx # /dashboard/settings
└── page.tsx # / -
動的ルーティング: 角括弧
[folderName]
を含むディレクトリを作成することで、動的なURLパラメータを持つルートを作成できます。
app/
└── products/
└── [productId]/ # 例: /products/123, /products/abc
└── page.tsx
page.tsx
コンポーネント内では、params
プロパティからURLパラメータを取得できます。
“`tsx
// app/products/[productId]/page.tsx
interface ProductPageProps {
params: {
productId: string;
};
}export default function ProductPage({ params }: ProductPageProps) {
const productId = params.productId;
return (Product ID: {productId}
{/ 商品詳細などを表示 /}
);
}
* **レイアウト `layout.tsx`:** 同じディレクトリとその子孫ルートセグメントで共有されるUI構造を定義します。例えば、サイドバーやナビゲーションバーなどを配置できます。レイアウトはネストすることも可能です。
app/
├── layout.tsx # ルートレイアウト (全てのページに適用)
└── dashboard/
├── layout.tsx # /dashboard とその子に適用
└── page.tsx
レイアウトコンポーネントは `children` プロパティを受け取り、その中に子要素(他のレイアウトやページ)がレンダリングされます。
tsx
// app/dashboard/layout.tsx
import Sidebar from ‘@/components/Sidebar’; // 仮のコンポーネントexport default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children} {/ 子要素がここにレンダリングされる /}
);
}
* **ローディングUI `loading.tsx`:** 動的なコンテンツをロードしている間に表示するUIを定義できます。
app/
└── dashboard/
├── loading.tsx # /dashboard のロード中に表示
└── page.tsx
* **エラーハンドリング `error.tsx`:** 子孫ルートセグメントでエラーが発生した場合に表示するUIを定義できます。
app/
└── dashboard/
├── error.tsx # /dashboard とその子でのエラー時に表示
└── page.tsx
* **APIルート `route.ts/js`:** バックエンドAPIエンドポイントを定義します。`app/api` ディレクトリ内に作成します。
app/api/
└── users/
└── route.ts # /api/users エンドポイント
`route.ts` ファイルでは、HTTPメソッドに対応する関数をexportします。
ts
// app/api/users/route.ts
import { NextResponse } from ‘next/server’;export async function GET(request: Request) {
// ユーザーデータを取得するロジック
const users = [{ id: 1, name: ‘Alice’ }, { id: 2, name: ‘Bob’ }];
return NextResponse.json(users);
}export async function POST(request: Request) {
const data = await request.json();
// ユーザーを作成するロジック
console.log(‘Creating user:’, data);
return NextResponse.json({ message: ‘User created’, user: data });
}
“`
App Routerは、Pages Routerに比べてよりリッチなルーティングとUI管理の機能を提供します。
4. コンポーネントとデータ取得(App Router)
App Routerの最も重要な特徴の一つは、デフォルトでReact Server Componentsが使用される点です。
-
サーバーコンポーネント (Server Components):
app
ディレクトリ内のほとんどのコンポーネントは、デフォルトでサーバーコンポーネントとして扱われます。- サーバーコンポーネントはサーバー上でレンダリングされ、JavaScriptバンドルには含まれません。これにより、クライアントに送信されるJavaScriptの量が減り、初期表示速度が向上します。
- サーバーコンポーネント内では、非同期処理(ファイルの読み込み、データベースアクセス、API呼び出しなど)を直接行うことができます。
- サーバーコンポーネントはクライアントの状態やブラウザAPI(
window
,localStorage
など)にアクセスできません。 - サーバーコンポーネント内でイベントハンドラ(
onClick
など)を定義することはできません。
“`tsx
// app/page.tsx (デフォルトでサーバーコンポーネント)
import Link from ‘next/link’;async function getUsers() {
// サーバーサイドで直接データをフェッチ
const res = await fetch(‘https://jsonplaceholder.typicode.com/users’);
if (!res.ok) {
throw new Error(‘Failed to fetch users’);
}
return res.json();
}export default async function HomePage() {
const users = await getUsers(); // await を使用して非同期データを取得return (
Users
-
{users.map((user: any) => (
- {user.name}
))}
About Page {/ Link コンポーネントはクライアントコンポーネント /}
);
}
“` -
クライアントコンポーネント (Client Components):
- クライアントコンポーネントは、従来のReactコンポーネントのようにブラウザでJavaScriptとして実行されます。
- インタラクティブなUI(ステートフルなコンポーネント、イベントハンドラを持つコンポーネントなど)を作成する場合に使用します。
- コンポーネントファイルの先頭に
'use client';
ディレクティブを記述することで、そのコンポーネントとその子孫コンポーネントがクライアントコンポーネントであることを示します。 - クライアントコンポーネント内では、
useState
,useEffect
などのHooksや、ブラウザAPIを使用できます。 - クライアントコンポーネントはサーバーコンポーネントを子として持つことができます。しかし、サーバーコンポーネントはクライアントコンポーネントを子として持つことはできません(ただし、子として渡されたクライアントコンポーネントは、サーバーコンポーネント内でレンダリングされる際にプレースホルダーとして扱われ、クライアント側でハイドレーションされます)。
“`tsx
// components/Counter.tsx
‘use client’; // これがクライアントコンポーネントであることを示すimport { useState } from ‘react’;
export default function Counter() {
const [count, setCount] = useState(0);return (
Count: {count}
);
}
“`“`tsx
// app/dashboard/page.tsx (サーバーコンポーネント)
import Counter from ‘@/components/Counter’; // クライアントコンポーネントをインポートexport default function DashboardPage() {
return (Dashboard
{/ サーバーコンポーネント内でクライアントコンポーネントを使用 /} );
}
“`
データ取得は、App Routerでは主にサーバーコンポーネント内で非同期処理として fetch
を使用するのが推奨される方法です。Next.jsは fetch
APIを拡張しており、デフォルトで自動的にデータのキャッシュや重複排除を行います。
“`tsx
// app/products/[productId]/page.tsx
interface ProductPageProps {
params: {
productId: string;
};
}
async function getProduct(productId: string) {
// fetch API が Next.js によって拡張されている
const res = await fetch(https://.../products/${productId}
);
// エラーハンドリング
if (!res.ok) {
// エラーコンポーネントに渡される
throw new Error(‘Failed to fetch product’);
}
// デフォルトでは fetch は自動的にキャッシュされる (SSGのような挙動)
// revalidate オプションで ISR のような挙動も可能
// const res = await fetch(https://.../products/${productId}
, { next: { revalidate: 60 } }); // 60秒ごとに再検証
// キャッシュしない場合は { cache: ‘no-store’ } を指定 (SSRのような挙動)
// const res = await fetch(https://.../products/${productId}
, { cache: ‘no-store’ });
return res.json();
}
export default async function ProductPage({ params }: ProductPageProps) {
const product = await getProduct(params.productId); // サーバーコンポーネント内で await
return (
{product.name}
{product.description}
Price: ${product.price}
);
}
“`
5. スタイリング
Next.jsでは、いくつかの方法でコンポーネントにスタイルを適用できます。
- グローバルCSS:
app/globals.css
(App Router) またはpages/_app.tsx
(Pages Router) でインポートすることで、アプリケーション全体に適用されるスタイルを記述できます。
-
CSS Modules:
- スタイルを定義するCSSファイルの拡張子を
.module.css
とすることで、CSS Modulesとして扱われます。これにより、クラス名が自動的にユニークになり、スタイルの衝突を防ぐことができます。
css
/* components/Button.module.css */
.button {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
}
“`tsx
// components/Button.tsx
‘use client’; // クライアントコンポーネントとして使用する場合
import styles from ‘./Button.module.css’;
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
}export default function Button({ children, onClick }: ButtonProps) {
return (
);
}
* **Styled JSX:**
tsx
* Next.jsに組み込まれているCSS-in-JSライブラリです。コンポーネント内で `<style jsx>` タグを使ってスタイルを記述できます。スコープ付きのスタイルを簡単に記述できます。
// components/StyledButton.tsx
export default function StyledButton({ children }: { children: React.ReactNode }) {
return (
<>
);
}
“`
* その他のライブラリ:
* Tailwind CSS, styled-components, Emotionなど、人気のCSSライブラリも簡単に導入して使用できます。 - スタイルを定義するCSSファイルの拡張子を
6. APIルート
APIルートを使用すると、Next.jsプロジェクト内にバックエンドコードを記述し、APIエンドポイントとして公開できます。これにより、データベースとの連携や外部APIの呼び出し、認証処理など、サーバーサイドで実行する必要のある処理を実装できます。
App Routerの場合、APIルートは app/api
ディレクトリ内に route.ts
(または .js
) ファイルとして作成します。
app/api/
├── hello/
│ └── route.ts # /api/hello
└── users/
├── route.ts # /api/users
└── [userId]/
└── route.ts # /api/users/[userId]
route.ts
ファイルでは、HTTPメソッドに対応する関数(GET
, POST
, PUT
, DELETE
, PATCH
, HEAD
, OPTIONS
)を export
します。これらの関数は Request
オブジェクトを受け取り、Response
オブジェクト(通常は NextResponse.json()
を使用)を返します。
“`ts
// app/api/hello/route.ts
import { NextResponse } from ‘next/server’;
export async function GET() {
return NextResponse.json({ message: ‘Hello, Next.js API Route!’ });
}
export async function POST(request: Request) {
const data = await request.json();
console.log(‘Received data:’, data);
return NextResponse.json({ message: ‘Data received’, data });
}
“`
動的なAPIルートでは、URLパラメータを関数に渡される params
オブジェクトから取得できます。
“`ts
// app/api/users/[userId]/route.ts
import { NextResponse } from ‘next/server’;
export async function GET(request: Request, { params }: { params: { userId: string } }) {
const userId = params.userId;
// ダミーのユーザーデータを返す例
const user = { id: userId, name: User ${userId}
};
if (!user) {
return NextResponse.json({ message: ‘User not found’ }, { status: 404 });
}
return NextResponse.json(user);
}
“`
クライアントサイドからは、標準の fetch
APIなどを使用してこれらのAPIルートを呼び出すことができます。
“`tsx
// クライアントコンポーネント内などから呼び出し
‘use client’;
import { useEffect, useState } from ‘react’;
export default function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function fetchUsers() {
const res = await fetch(‘/api/users’); // APIルートを呼び出し
const data = await res.json();
setUsers(data);
}
fetchUsers();
}, []);
return (
User List
-
{users.map((user: any) => (
- {user.name}
))}
);
}
“`
APIルートはサーバーレス関数としてデプロイされるため、スケーラブルなバックエンド処理を簡単に実現できます。
7. デプロイ
Next.jsアプリケーションのデプロイは非常に簡単です。特にVercelを使用する場合、GitリポジトリをVercelに連携させるだけで、自動的にビルド・デプロイが行われ、URLが発行されます。プッシュするたびに新しいバージョンが自動的にデプロイされる継続的インテグレーション/デプロイ(CI/CD)環境も容易に構築できます。
Vercel以外にも、以下の様々なプラットフォームにデプロイ可能です。
- Netlify: Build Settingsで
npm run build
またはyarn build
、Publish directoryに.next
を指定することでデプロイできます。SSRやISRの機能もサポートされていますが、Vercelに比べて一部制限がある場合があります。 - AWS Amplify: Next.jsのSSR/SSG/ISRに対応しており、簡単にデプロイできます。
- Render: Node.jsアプリケーションとしてデプロイ可能です。
- Heroku: buildpackを使用することでデプロイできます。
- Docker: Next.jsアプリケーションをDockerコンテナ化して、任意のコンテナ実行環境(ECS, Kubernetesなど)にデプロイすることも可能です。
デプロイ先の選択は、プロジェクトの要件や既存のインフラストラクチャによって決定されます。
Next.jsのレンダリング戦略の詳細
Next.jsの最大の強みの一つは、ページごとに最適なレンダリング戦略を選択できることです。ここでは、それぞれの戦略についてもう少し詳しく掘り下げてみましょう。App Routerのデータ取得とレンダリング戦略の関連性についても解説します。
1. クライアントサイドレンダリング (Client-Side Rendering – CSR)
- 仕組み: サーバーからは最小限のHTML(通常、アプリケーションのエントリーポイントとなる
<div id="root"></div>
のような要素を含むだけ)が送信されます。ブラウザはHTMLを受け取った後、JavaScriptファイルをダウンロードし、Reactを実行してDOMを構築し、UIを描画します。データが必要な場合は、ブラウザからAPIを呼び出してデータを取得し、取得後にUIを更新します。 - Next.jsでの実現方法: App Routerでは、
'use client'
ディレクティブを使用するクライアントコンポーネント内で、useEffect
フックやデータ取得ライブラリ(SWR, React Queryなど)を使ってデータをフェッチし、コンポーネントの状態として管理することでCSRを実現します。Pages Routerでは、useEffect
フック内でデータをフェッチする方法が一般的です。 - メリット:
- 初期レンダリング後のページ遷移が高速でスムーズです(SPAの特徴)。
- インタラクティブなUIや複雑なクライアントサイドロジックを持つアプリケーションに適しています。
- デメリット:
- 初期表示が遅くなる可能性があります(JavaScriptのダウンロード・実行が完了するまで画面が空白になる)。
- SEOに不利な場合があります(クローラーがJavaScriptを実行できない場合、コンテンツを認識できない)。
- ユーザーがインタラクションを開始できるようになるまで時間がかかる(Time To Interactive – TTI)。
- 使い所: ユーザー認証後の管理画面や、ログインが必要なダッシュボードなど、SEOがそれほど重要ではなく、豊富なクライアントサイドのインタラクションが必要なページ。
2. サーバーサイドレンダリング (Server-Side Rendering – SSR)
- 仕組み: ユーザーからのリクエストがあるたびに、サーバー上でReactコンポーネントをHTML文字列にレンダリングします。生成されたHTMLは、ページに必要な初期データを含んだ状態でブラウザに送信されます。ブラウザは受け取ったHTMLをすぐに表示できるため、初期表示が高速です。その後、ブラウザ側でJavaScriptがダウンロード・実行され、DOMとサーバーで生成されたHTMLが紐付けられ、アプリケーションがインタラクティブになります(このプロセスをハイドレーションと呼びます)。
-
Next.jsでの実現方法:
- Pages Router:
pages
ディレクトリのページコンポーネントでgetServerSideProps
という非同期関数をエクスポートします。この関数内でデータをフェッチし、そのデータをページのPropsとして返します。 - “`tsx
// pages/ssr-page.tsx (Pages Router)
import { GetServerSideProps } from ‘next’;
interface SsrPageProps {
data: any;
}export const getServerSideProps: GetServerSideProps
= async (context) => {
// リクエストごとにサーバーサイドで実行される
const res = await fetch(‘https://…/dynamic-data’);
const data = await res.json();return { props: { data }, // ページコンポーネントにPropsとして渡される };
};
export default function SsrPage({ data }: SsrPageProps) {
return (SSR Page
Data: {JSON.stringify(data)}
);
}
* **App Router:** デフォルトの挙動がSSRに近いです。サーバーコンポーネント内で非同期の `fetch` を使用し、キャッシュを無効にする (`{ cache: 'no-store' }`) ことで、リクエストごとにデータをフェッチし、サーバーでレンダリングするSSRを実現できます。
tsx
*
// app/ssr-page/page.tsx (App Router)
async function getDynamicData() {
// リクエストごとに実行される fetch (cache: ‘no-store’ を指定)
const res = await fetch(‘https://…/dynamic-data’, { cache: ‘no-store’ });
if (!res.ok) {
throw new Error(‘Failed to fetch dynamic data’);
}
return res.json();
}export default async function SsrPage() {
const data = await getDynamicData(); // サーバーコンポーネントで非同期データ取得return ( <div> <h1>SSR Page (App Router)</h1> <p>Data: {JSON.stringify(data)}</p> </div> );
}
“`
* メリット:
* 初期表示が高速です。
* SEOに有利です(クローラーがHTMLを直接読み取れる)。
* 動的なコンテンツやユーザー固有のコンテンツを表示するページに適しています。
* デメリット:
* リクエストごとにサーバーでページを生成するため、サーバー負荷が高くなる可能性があります。
* ページがインタラクティブになるまでには、ハイドレーションが完了するのを待つ必要があります。
* 使い所: ユーザーログインの状態によってコンテンツが変わるページ、最新情報が常に表示される必要があるニュースサイトのトップページ、eコマースサイトの商品詳細ページ(在庫状況など動的な情報が多い場合)など。 - Pages Router:
3. 静的サイト生成 (Static Site Generation – SSG)
- 仕組み: ビルド時にあらかじめすべてのページをHTMLファイルとして生成します。生成された静的なHTMLファイルは、CDNに配置されます。ユーザーからのリクエストがあった際は、CDNから最も近いエッジロケーションにあるHTMLファイルが配信されるため、非常に高速な表示が可能です。JavaScriptは、ブラウザでハイドレーションを行うために後から読み込まれます。
-
Next.jsでの実現方法:
- Pages Router:
pages
ディレクトリのページコンポーネントでgetStaticProps
という非同期関数をエクスポートします。動的なパスを持つページ(例: ブログ記事の詳細ページ/posts/[id]
)の場合は、さらにgetStaticPaths
という非同期関数をエクスポートし、ビルド時に生成すべきパス(/posts/1
,/posts/2
など)のリストを指定します。 - “`tsx
// pages/posts/[id].tsx (Pages Router)
import { GetStaticPaths, GetStaticProps } from ‘next’;
interface PostPageProps {
post: {
id: string;
title: string;
content: string;
};
}export const getStaticPaths: GetStaticPaths = async () => {
// ビルド時に生成するパスのリストを取得
const posts = await fetch(‘https://…/posts’).then(res => res.json());
const paths = posts.map((post: any) => ({
params: { id: post.id.toString() },
}));return { paths, fallback: false, // 事前生成されていないパスへのアクセスを404にする // fallback: 'blocking' または true を指定することで ISR を実現 };
};
export const getStaticProps: GetStaticProps
= async ({ params }) => {
// ビルド時に各パスに対して一度だけ実行される
const post = await fetch(https://.../posts/${params?.id}
).then(res => res.json());return { props: { post }, };
};
export default function PostPage({ post }: PostPageProps) {
return ({post.title}
{post.content}
);
}
* **App Router:** デフォルトの挙動がSSGに近いです。サーバーコンポーネント内で非同期の `fetch` を使用し、デフォルトのキャッシュ挙動(データは永続的にキャッシュされる)を利用することでSSGを実現できます。動的なパスを持つページの場合は、`generateStaticParams` 関数を使用してビルド時に生成すべきパスを定義します。
tsx
*
// app/posts/[id]/page.tsx (App Router)
async function getPost(id: string) {
// fetch API のデフォルトキャッシュ挙動 (永続キャッシュ)
const res = await fetch(https://.../posts/${id}
);
if (!res.ok) {
throw new Error(‘Failed to fetch post’);
}
return res.json();
}export async function generateStaticParams() {
// ビルド時に生成するパスのリストを取得
const posts = await fetch(‘https://…/posts’).then(res => res.json());return posts.map((post: any) => ({ id: post.id.toString(), }));
}
interface PostPageProps {
params: {
id: string;
};
}export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.id); // サーバーコンポーネントで awaitreturn ( <div> <h1>{post.title}</h1> <p>{post.content}</p> </div> );
}
“`
* メリット:
* 最も高速な表示が可能です(ビルド済みのHTMLをCDNから配信するため)。
* サーバー負荷が非常に低いです(リクエスト時にサーバーサイドでのレンダリングが不要)。
* SEOに非常に有利です。
* デメリット:
* コンテンツを更新した場合、サイト全体を再ビルド・再デプロイする必要があります(ISRで緩和可能)。
* ユーザー固有のコンテンツや頻繁に変化するコンテンツには向いていません(ビルド時にデータが固定されるため)。
* 使い所: ブログ、ドキュメントサイト、コーポレートサイト、マーケティングサイトなど、コンテンツがあまり頻繁に更新されず、高速な表示とSEOが重要なページ。 - Pages Router:
4. 増分静的再生成 (Incremental Static Regeneration – ISR)
- 仕組み: SSGの利点を維持しつつ、コンテンツの更新に柔軟に対応するための戦略です。ビルド時にページを生成することに加え、指定した時間間隔(
revalidate
オプション)が経過した後、または特定のイベント(Webhookなど)をトリガーに、バックグラウンドでページを再生成します。ユーザーは古いページを表示している間に新しいページが生成され、次にそのページにアクセスした際に新しいページが表示されるようになります。 -
Next.jsでの実現方法:
- Pages Router:
getStaticProps
関数で返されるオブジェクトにrevalidate
オプション(秒単位)を追加します。getStaticPaths
のfallback
オプションをtrue
または'blocking'
に設定することで、ビルド時に生成されなかったパスへのアクセスがあった際にも、初回アクセス時にサーバーサイドでページを生成し、その後そのページをキャッシュして以降のリクエストでは静的に配信する、という挙動も実現できます。 - “`tsx
// pages/products/[id].tsx (Pages Router, ISR)
import { GetStaticPaths, GetStaticProps } from ‘next’;
// … (インターフェースなどの定義はSSGの例と同様)
export const getStaticPaths: GetStaticPaths = async () => {
// 少数の人気商品だけビルド時に生成
const initialProducts = await fetch(‘https://…/products/popular’).then(res => res.json());
const paths = initialProducts.map((product: any) => ({
params: { id: product.id.toString() },
}));return { paths, fallback: 'blocking', // 事前生成されていないパスへのアクセス時にサーバーで生成し、その後キャッシュする };
};
export const getStaticProps: GetStaticProps
= async ({ params }) => {
const product = await fetch(https://.../products/${params?.id}
).then(res => res.json());if (!product) { return { notFound: true }; // ページが見つからない場合は404 } return { props: { product }, revalidate: 60, // 60秒ごとに再検証 };
};
export default function ProductPage({ product }: ProductPageProps) {
// … (コンポーネントの中身はSSGの例と同様)
}
* **App Router:** `fetch` APIのオプションで `next: { revalidate: number | false }` を設定することでISRを実現できます。`revalidate: number` は指定した秒数ごとにバックグラウンドで再検証を行います。また、`app` ディレクトリ内のレイアウトやページ単位で `export const revalidate = number | false | 'force-cache' | 'only-cache' | 'no-store'` という変数をエクスポートすることでも、そのセグメント内のすべての `fetch` の再検証設定を上書きできます。
tsx
*
// app/products/[id]/page.tsx (App Router, ISR)
// 個別の fetch 呼び出しで revalidate を設定
async function getProduct(id: string) {
const res = await fetch(https://.../products/${id}
, { next: { revalidate: 60 } }); // 60秒ごとに再検証
if (!res.ok) {
throw new Error(‘Failed to fetch product’);
}
return res.json();
}// generateStaticParams は SSG と同様にビルド時に生成するパスを指定
export async function generateStaticParams() {
// … (SSGの例と同様)
}interface ProductPageProps {
params: {
id: string;
};
}export default async function ProductPage({ params }: ProductPageProps) {
const product = await getProduct(params.id);return ( <div> <h1>{product.name}</h1> <p>{product.description}</p> <p>Price: ${product.price}</p> </div> );
}
*
tsx
// app/dashboard/page.tsx (App Router, ISR – セグメント単位)
// このセグメント内のすべての fetch はデフォルトで60秒ごとに再検証される
export const revalidate = 60;async function getDashboardData() {
// revalidate が 60 に設定されているため、fetch は 60秒ごとに再検証される
const res = await fetch(‘https://…/dashboard-data’);
if (!res.ok) {
throw new Error(‘Failed to fetch dashboard data’);
}
return res.json();
}export default async function DashboardPage() {
const data = await getDashboardData();return ( <div> <h1>Dashboard</h1> <p>Last updated: {new Date().toLocaleString()}</p> <pre>{JSON.stringify(data, null, 2)}</pre> </div> );
}
“`
* メリット:
* SSGの高速性を維持しつつ、コンテンツの更新にも対応できます。
* ビルド時間を短縮できます(すべてのページをビルドする必要がないため)。
* SSGとSSRの中間的なアプローチとして、多くの種類のWebサイトに適しています。
* デメリット:
* ユーザーが最新のコンテンツを見るまでに、再検証が完了するまで少し時間がかかる場合があります。
* 再検証のための設定が必要です。
* 使い所: ブログサイト(新しい記事が公開された際に記事一覧を再生成)、eコマースサイトの商品一覧・詳細ページ(在庫や価格が変動するが、リアルタイム性はそこまで要求されない場合)、ニュースサイトなど、コンテンツが頻繁に更新されるが、すべてのリクエストでサーバーレンダリングするほどのリアルタイム性やサーバー負荷を避けたいページ。 - Pages Router:
App Routerにおけるデータ取得とレンダリング戦略の統一
App Routerでは、Pages Routerの getStaticProps
, getServerSideProps
, getInitialProps
といった特定のデータ取得関数がなくなり、代わりにサーバーコンポーネント内での fetch
APIの挙動と、revalidate
オプションや cache
オプションの設定によって、レンダリング戦略(SSG, SSR, ISR)を制御するようになっています。
- デフォルト: サーバーコンポーネントでの
fetch
は、デフォルトでSSGのようにデータをキャッシュし、ビルド時に静的なHTMLを生成しようとします。 { cache: 'no-store' }
またはrevalidate: 0
: リクエストごとにデータをフェッチし、SSRのように振る舞います。{ next: { revalidate: number } }
またはexport const revalidate = number;
: 指定した秒数ごとにデータを再検証し、ISRのように振る舞います。{ cache: 'force-cache' }
: データ永続キャッシュし、SSGのように振る舞います(デフォルトと同じ)。
このように、App Routerではデータ取得方法が統一され、キャッシュ戦略の指定によってレンダリング方法を柔軟に制御できるようになりました。これにより、より直感的で効率的な開発が可能になっています。
5. ハイブリッドなアプローチ
Next.jsの強力な点は、これらのレンダリング戦略を同じアプリケーション内でページごとに、あるいはルートセグメントごとに組み合わせて使用できることです。例えば、ブログのトップページ(最新記事を表示)はSSR、個別の記事ページはISR、会社概要ページのような静的なページはSSG、ログイン後のダッシュボードはCSRといったように、ページの特性に合わせて最適な戦略を選択することで、アプリケーション全体のパフォーマンスを最大化できます。
Next.jsの高度な機能
Next.jsは、基本的な機能以外にも、プロダクションレベルのアプリケーション開発に役立つ様々な高度な機能を提供しています。
1. 画像の最適化 (next/image
)
Webサイトの表示速度において、最適化されていない画像は最大のボトルネックの一つです。next/image
コンポーネントは、これらの問題を自動的に解決するための優れた機能です。
- 自動最適化: ビルド時またはオンデマンド(リクエスト時)に、画像のサイズ変更、フォーマット変換(WebPなど)、最適化を自動的に行います。
- 遅延読み込み (Lazy Loading): ページが表示された時点で見えていない画像は、スクロールされてビューポートに入るまで読み込みを遅延させます。
- デバイスサイズ対応: ユーザーのデバイスサイズや画面解度に応じて、最適なサイズの画像を自動的に提供します(srcset属性を自動生成)。
- レイアウトシフトの防止: 画像の読み込み前に領域を確保することで、画像の読み込みによるコンテンツのガタつき(Cumulative Layout Shift – CLS)を防ぎます。
- 必須プロパティ:
src
、alt
に加えて、画像のレイアウトを定義するためにwidth
とheight
、またはlayout="fill"
(親要素にサイズが依存する場合) が必須です。これにより、ブラウザが画像のロード前に領域を確保できるようになります。
“`tsx
import Image from ‘next/image’;
import profilePic from ‘../public/profile.jpg’; // ローカル画像の場合、import する
export default function HomePage() {
return (
My Homepage
{/ ローカル画像 /}
{/* 外部URLの画像 */}
<Image
src="https://via.placeholder.com/600/92c952"
alt="Placeholder image"
width={600} // 必須
height={400} // 必須
// 外部URLの場合、next.config.js で domains または remotePatterns の設定が必要
/>
{/* 親要素のサイズに合わせてフィル */}
<div style={{ position: 'relative', width: '300px', height: '200px' }}>
<Image
src="https://via.placeholder.com/600/771796"
alt="Another placeholder image"
fill // 親要素に合わせてフィルする場合
style={{ objectFit: 'cover' }} // スタイルも適用可能
/>
</div>
</div>
);
}
``
next/image
外部URLの画像をで使用する場合は、セキュリティ上の理由から
next.config.js` で許可するドメインまたはパターンを指定する必要があります。
“`js
// next.config.js
/ @type {import(‘next’).NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: ‘https’,
hostname: ‘via.placeholder.com’,
port: ”,
pathname: ‘/‘,
},
],
// または domains: [‘via.placeholder.com’], (よりシンプルな設定)
},
};
module.exports = nextConfig;
“`
2. フォントの最適化 (next/font
)
Webフォントはデザイン性を向上させますが、読み込みが遅いとテキストが表示されるまで時間がかかったり、表示後にレイアウトがずれたり(CLS)する原因になります。next/font
はこれらの問題を解決します。
- 自動ホスティング: Google Fontsやローカルのフォントファイルを自動的にダウンロードし、プロジェクトと同じドメインから提供します。これにより、外部へのリクエストが不要になり、パフォーマンスが向上します。
- CLSの防止: フォントの読み込み中に代替フォントを表示し、フォントが読み込まれた際にレイアウトのずれを防ぎます(
display: swap
に相当する機能が自動で適用されます)。 - FOUC (Flash Of Unstyled Content) の防止: CSSとフォントファイルを同時にロードし、スタイルが適用される前に生のテキストが表示される現象を防ぎます。
- サブセット化: 使用している文字だけを含むフォントファイルを作成し、ファイルサイズを削減します。
“`tsx
// app/layout.tsx または ページファイル
import { Inter } from ‘next/font/google’; // Google Fonts の例
const inter = Inter({ subsets: [‘latin’] });
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{/ html 要素にフォントクラスを適用 /}
);
}
“`
ローカルフォントを使用する場合は、以下のように記述します。
“`tsx
import LocalFont from ‘next/font/local’;
const myFont = LocalFont({
src: ‘./my-font.woff2’, // プロジェクト内のフォントファイルパス
display: ‘swap’, // オプション: フォントが利用可能になるまで代替フォントを表示
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
);
}
“`
3. スクリプトの最適化 (next/script
)
サードパーティ製のスクリプト(アナリティクス、広告タグ、ウィジェットなど)は、ページの読み込み速度に悪影響を与えることがあります。next/script
コンポーネントは、これらのスクリプトの読み込みタイミングを最適化します。
- 戦略の指定:
strategy
プロパティを使用して、スクリプトの読み込み戦略を指定できます。beforeInteractive
: ページがインタラクティブになる前に読み込みます。高優先度で実行する必要があるスクリプトに適しています(例: Google Tag Manager)。afterInteractive
(デフォルト): ページがインタラクティブになった後に読み込みます。ほとんどのサードパーティスクリプトに適しています(例: Google Analytics)。lazyOnload
: アイドル状態(ページ読み込みが完了し、ネットワークやCPUが使用されていない状態)になった後に遅延読み込みします。重要度の低いスクリプトに適しています(例: チャットボット)。worker
: Web Worker で実行します(まだ実験的な機能)。
- 遅延読み込み:
beforeInteractive
以外では、自動的に遅延読み込みが行われます。
“`tsx
import Script from ‘next/script’;
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
);
}
“`
4. 環境変数
開発環境、ステージング環境、本番環境など、環境によって異なる設定値(APIキー、データベースURLなど)を使用したい場合があります。Next.jsでは、環境変数を簡単に管理できます。
.env.development
,.env.production
,.env.local
などのファイルを作成し、その中にKEY=VALUE
の形式で環境変数を記述します。- クライアントサイドのコードからアクセスしたい環境変数は、変数名の先頭に
NEXT_PUBLIC_
を付けます。これ以外の環境変数はサーバーサイドのコード(APIルートやサーバーコンポーネントなど)からのみアクセス可能です。 process.env.YOUR_VARIABLE_NAME
の形式で環境変数にアクセスできます。
“`
.env.local
このファイルは .gitignore に含めるべきです
NEXT_PUBLIC_ANALYTICS_ID=abc-123 # クライアントからアクセス可能
DATABASE_URL=mongodb://localhost:27017/mydb # サーバーからのみアクセス可能
“`
“`ts
// サーバーサイド (APIルートやサーバーコンポーネント)
const dbUrl = process.env.DATABASE_URL;
console.log(‘Database URL:’, dbUrl);
// クライアントサイド (クライアントコンポーネント)
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;
console.log(‘Analytics ID:’, analyticsId);
“`
5. ミドルウェア (Middleware)
Next.js 12から導入されたミドルウェア機能を使用すると、リクエストが完了する前にコードを実行できます。これにより、認証、リダイレクト、ヘッダーの書き換え、A/Bテストなど、様々な処理をエッジで実行できます。
- ルートディレクトリ(
src
ディレクトリを使用している場合はsrc/
の直下、そうでなければプロジェクトのルート)にmiddleware.ts
(または.js
) ファイルを作成します。 - このファイルで
middleware
関数をエクスポートします。この関数はNextRequest
オブジェクトを受け取り、NextResponse
オブジェクトを返します。 config
オブジェクトで、ミドルウェアを実行するパスを指定できます。
“`ts
// middleware.ts
import { NextResponse } from ‘next/server’;
import type { NextRequest } from ‘next/server’;
// ミドルウェアを実行するパスを指定
export const config = {
matcher: [‘/dashboard/:path‘, ‘/api/users/:path‘], // /dashboard または /api/users 配下の全てのパス
};
export function middleware(request: NextRequest) {
const isAuthenticated = checkAuthStatus(request); // 仮の認証チェック関数
// /dashboard に認証されていないユーザーがアクセスした場合
if (request.nextUrl.pathname.startsWith(‘/dashboard’)) {
if (!isAuthenticated) {
// ログインページにリダイレクト
return NextResponse.redirect(new URL(‘/login’, request.url));
}
}
// /api/users に認証されていないユーザーがアクセスした場合
if (request.nextUrl.pathname.startsWith(‘/api/users’)) {
if (!isAuthenticated) {
// エラーレスポンスを返す
return new NextResponse(‘Authentication Required’, { status: 401 });
}
}
// それ以外の場合はそのまま通過
return NextResponse.next();
}
// 仮の認証チェック関数 (実際はCookieやトークンなどをチェック)
function checkAuthStatus(request: NextRequest): boolean {
const authCookie = request.cookies.get(‘auth-token’);
return !!authCookie; // 例: クッキーが存在すれば認証済みとする
}
“`
ミドルウェアは、サーバーサイドのAPIルートよりも前に実行されるため、共通の認証処理などを効率的に実装できます。また、エッジ環境で実行されるため、高速な処理が期待できます。
6. 国際化 (i18n)
Next.jsは、Webサイトを多言語化するための機能もサポートしています。next.config.js
でロケール(言語コード)やデフォルトロケール、ドメインごとのロケールなどを設定できます。
“`js
// next.config.js
/* @type {import(‘next’).NextConfig} /
const nextConfig = {
i18n: {
locales: [‘en’, ‘ja’, ‘fr’], // サポートするロケール
defaultLocale: ‘en’, // デフォルトロケール
// domain: true, // ドメインごとのロケール設定を有効にする場合
},
};
module.exports = nextConfig;
“`
設定すると、Next.jsは自動的にURLにロケール情報を付与したり(/ja/about
)、Accept-Language
ヘッダーに基づいて最適なロケールにリダイレクトしたりします。コンテンツの翻訳自体は別途ライブラリ(next-i18nextなど)や自前で実装する必要がありますが、ルーティング周りの基盤をNext.jsが提供してくれます。
7. テスト
Next.jsアプリケーションのテストは、一般的なReactアプリケーションのテストと同様に行えます。
- ユニットテスト: JestやReact Testing Libraryを使用して、個々のコンポーネントや関数をテストします。
- 結合テスト: 複数のコンポーネントやページ間の連携をテストします。React Testing LibraryやCypressなどを使用できます。
- E2Eテスト: CypressやPlaywrightなどを使用して、ブラウザ上でのユーザー操作フロー全体をテストします。
Next.jsは特別なテストフレームワークを提供していませんが、上記の一般的なJavaScript/Reactのテストツールと組み合わせて使用できます。
8. 型安全な開発 (TypeScript)
create-next-app
でTypeScriptを選択した場合、プロジェクトは最初からTypeScriptでセットアップされます。これにより、静的解析によるバグの早期発見やコード補完による開発効率向上など、TypeScriptの恩恵を受けることができます。Next.js自体もTypeScriptで記述されており、型定義が提供されています。
9. 静的解析 (ESLint)
create-next-app
でESLintを選択した場合、ESLintがセットアップされ、Next.js推奨の設定が適用されます。これにより、コードの品質を一定に保ち、潜在的な問題を検出できます。
10. コードフォーマット (Prettier)
ESLintと同様に、Prettierなどのコードフォーマッターを導入することで、コードの整形を自動化し、チーム内でのコーディング規約を統一できます。
これらの高度な機能を活用することで、大規模かつ高品質なNext.jsアプリケーションを効率的に開発・運用することが可能になります。
Pages Router vs App Router
Next.jsを学ぶ上で、Pages RouterとApp Routerの違いを理解することは非常に重要です。Next.js 13でApp Routerが登場し、開発の推奨スタイルが大きく変化しました。
Pages Routerの歴史と特徴
Pages RouterはNext.jsの初期バージョンから存在していたルーティングシステムです。
- 特徴:
pages
ディレクトリ内のファイルに基づいてルーティングを定義します。- ページコンポーネントは基本的にクライアントサイドでハイドレーションされることを前提としています。
- データ取得には
getStaticProps
,getServerSideProps
,getInitialProps
といった関数を使用します。 - APIルートは
pages/api
ディレクトリ内に定義します。 - レイアウトは
_app.tsx
と_document.tsx
、またはページコンポーネント内で手動で共通コンポーネントを配置することで実現していました。
Pages Routerはシンプルで直感的であり、多くのNext.jsアプリケーションがこれを使用して構築されてきました。
App Routerの登場背景(React Server Componentsの導入)
App Routerは、React 18で導入されたReact Server Components (RSC)の概念をNext.jsに統合するために開発されました。RSCは、パフォーマンスと開発体験を向上させるための新しいアプローチです。
- RSCの目的:
- サーバーサイドでコンポーネントをレンダリングし、クライアントに送信されるJavaScriptの量を減らす。
- サーバーサイドのリソース(データベースなど)に直接アクセスできるようにする。
- 滝のように発生するデータ取得(Fetch Waterfall)を解消する。
- レンダリング戦略の選択肢を増やす。
App Routerは、このRSCの機能をNext.jsのルーティングとデータ取得、レンダリングシステムに組み込んだものです。
App Routerの主な特徴
前述の「基本的な使い方」セクションでも触れましたが、App Routerの主な特徴を改めて挙げます。
app
ディレクトリ: ルーティングとUI構造の主要な場所です。- デフォルトでサーバーコンポーネント: パフォーマンス最適化のために、コンポーネントはデフォルトでサーバーコンポーネントとして扱われます。クライアントインタラクションが必要な場合は
'use client'
を使用します。 - データ取得の統一: サーバーコンポーネント内での非同期
fetch
が推奨されるデータ取得方法となり、キャッシュ戦略の設定によってSSG, SSR, ISRを制御します。Pages Routerの特定のデータ取得関数は不要になります。 - レイアウト:
layout.tsx
ファイルを使用して、ディレクトリ構造に基づいたネスト可能なレイアウトを簡単に定義できます。 - ローディングUI:
loading.tsx
ファイルを使用して、非同期リソースのロード中に表示するUIを定義できます。 - エラーハンドリング:
error.tsx
ファイルを使用して、特定ルートセグメント内のエラーをキャッチして表示するUIを定義できます。 - ストリーミング: サーバーコンポーネントはストリーミングをサポートしており、ページのコンテンツをチャンクごとにブラウザに送信し、表示を速く開始できます。
どちらを使うべきか?移行の検討
- 新規プロジェクト: 公式にはApp Routerの使用が推奨されています。新しい機能(RSC, ネスト可能なレイアウトなど)を活用でき、今後のNext.jsの進化にも対応しやすいからです。
- 既存のPages Routerプロジェクト: 急いでApp Routerに移行する必要はありません。Pages Routerは引き続きサポートされており、多くのアプリケーションで十分に機能します。ただし、徐々にApp Routerに移行していくことは検討する価値があります。App RouterとPages Routerは共存できるため、新しい機能を追加する際にApp Routerを使用したり、既存のページを少しずつApp Routerに移行したりすることも可能です。
- 学習コスト: App RouterはRSCという新しいパラダイムを導入しているため、Pages Routerに慣れている開発者にとっては学習コストがかかります。しかし、長期的に見れば、RSCはReactの未来の一部となる可能性が高く、学ぶ価値は大きいでしょう。
App Routerはまだ発展途上の部分もありますが、ReactとNext.jsの将来的な方向性を示しており、特にパフォーマンスや開発体験の面で大きなメリットをもたらす可能性を秘めています。
Next.jsを利用した開発の実践例(簡単なブログ)
ここでは、Next.jsのApp Routerを使用して、Markdown形式のブログ記事を表示する簡単なブログサイトの例を見てみましょう。SSGとCSRを組み合わせて使用します。
1. 記事データの準備
ブログ記事はMarkdownファイルとして用意し、プロジェクト内の posts
ディレクトリに配置します。
my-next-app/
├── app/
│ ├── posts/
│ │ ├── [slug]/
│ │ │ └── page.tsx # 各記事詳細ページ
│ │ └── page.tsx # 記事一覧ページ
│ ├── layout.tsx
│ └── page.tsx
├── posts/ # Markdownファイルを置くディレクトリ
│ ├── first-post.md
│ └── second-post.md
├── lib/ # 記事データを読み込むための関数
│ └── posts.ts
├── public/
├── ...
記事のMarkdownファイル例 (posts/first-post.md
):
“`markdown
title: はじめてのブログ記事
date: 2023-10-27
これは私のはじめてのブログ記事です。
Next.jsでブログを作成するのはとても簡単ですね。
“`
2. 記事データの読み込みとパース
lib/posts.ts
を作成し、Markdownファイルを読み込んでメタデータとコンテンツを抽出する関数を作成します。gray-matter
というライブラリを使用します。
まず、必要なライブラリをインストールします。
“`bash
npm install gray-matter remark remark-html
または yarn add gray-matter remark remark-html
または pnpm add gray-matter remark remark-html
“`
lib/posts.ts
:
“`ts
import fs from ‘fs’;
import path from ‘path’;
import matter from ‘gray-matter’;
import { remark } from ‘remark’;
import html from ‘remark-html’;
// posts ディレクトリのパス
const postsDirectory = path.join(process.cwd(), ‘posts’);
export interface PostData {
slug: string;
title: string;
date: string;
contentHtml: string;
}
// 全ての記事のメタデータ (slug, title, date) を取得する関数
export function getSortedPostsData(): { slug: string; title: string; date: string }[] {
// /posts ディレクトリ以下のファイル名を取得
const fileNames = fs.readdirSync(postsDirectory);
const allPostsData = fileNames.map((fileName) => {
// .md をファイル名から削除して slug を取得
const slug = fileName.replace(/.md$/, ”);
// マークダウンファイルを文字列として読み込む
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, 'utf8');
// gray-matter を使ってメタデータセクションをパース
const matterResult = matter(fileContents);
// データを返す
return {
slug,
title: matterResult.data.title,
date: matterResult.data.date,
};
});
// 日付で記事をソート
return allPostsData.sort((a, b) => {
if (a.date < b.date) {
return 1;
} else {
return -1;
}
});
}
// slug に基づいて単一の記事データを取得する関数
export async function getPostData(slug: string): Promise
const fullPath = path.join(postsDirectory, ${slug}.md
);
const fileContents = fs.readFileSync(fullPath, ‘utf8’);
// gray-matter を使ってメタデータとコンテンツをパース
const matterResult = matter(fileContents);
// remark を使ってマークダウンを HTML に変換
const processedContent = await remark()
.use(html)
.process(matterResult.content);
const contentHtml = processedContent.toString();
// データを返す
return {
slug,
title: matterResult.data.title,
date: matterResult.data.date,
contentHtml,
};
}
// 動的パス生成のために全ての slug を取得する関数
export function getAllPostSlugs(): { slug: string }[] {
const fileNames = fs.readdirSync(postsDirectory);
return fileNames.map((fileName) => {
return {
slug: fileName.replace(/.md$/, ”),
};
});
}
“`
3. 記事一覧ページの作成 (app/posts/page.tsx
)
app/posts/page.tsx
を作成し、getSortedPostsData
を使用して記事一覧を表示します。このページはSSGで生成します。
“`tsx
// app/posts/page.tsx
import Link from ‘next/link’;
import { getSortedPostsData } from ‘@/lib/posts’; // @/ は import alias です
export default async function PostsPage() {
// サーバーコンポーネントでデータを取得 (デフォルトはキャッシュされ SSG のように振る舞う)
const allPostsData = getSortedPostsData();
return (
Blog Posts
-
{allPostsData.map(({ slug, title, date }) => (
-
/posts/${slug}}>
{title}
{date}
))}
);
}
“`
4. 記事詳細ページの作成 (app/posts/[slug]/page.tsx
)
app/posts/[slug]/page.tsx
を作成し、個別の記事詳細を表示します。動的ルーティング ([slug]
) を使用し、SSGで事前にページを生成します。generateStaticParams
関数で、ビルド時に生成すべきすべての記事パスを指定します。
“`tsx
// app/posts/[slug]/page.tsx
import { getPostData, getAllPostSlugs, PostData } from ‘@/lib/posts’; // @/ は import alias です
import Link from ‘next/link’;
// ビルド時に生成するパスを定義 (SSG のための generateStaticParams)
export async function generateStaticParams() {
const slugs = getAllPostSlugs();
return slugs.map((slug) => ({
slug: slug.slug,
}));
}
interface PostPageProps {
params: {
slug: string;
};
}
export default async function PostPage({ params }: PostPageProps) {
const postData: PostData = await getPostData(params.slug); // サーバーコンポーネントでデータ取得 (デフォルトはキャッシュされ SSG のように振る舞う)
return (
{postData.title}
{postData.date}
{/ dangerouslySetInnerHTML を使用して HTML コンテンツを表示 /}
);
}
“`
5. 開発サーバーの確認
npm run dev
で開発サーバーを起動し、http://localhost:3000/posts
および http://localhost:3000/posts/first-post
などにアクセスして、ブログページが表示されるか確認してください。
6. ISRの導入 (オプション)
もし記事が頻繁に更新される可能性がある場合、ISRを導入してビルド後に記事を再検証・再生成するように設定できます。app/posts/[slug]/page.tsx
に revalidate
オプションを追加します。
“`tsx
// app/posts/[slug]/page.tsx (ISR を有効にする場合)
import { getPostData, getAllPostSlugs, PostData } from ‘@/lib/posts’;
import Link from ‘next/link’;
// このページの再検証間隔を定義 (60秒ごとにバックグラウンドで再検証)
export const revalidate = 60; // 60秒
// generateStaticParams は SSG と同様に定義
interface PostPageProps {
params: {
slug: string;
};
}
export default async function PostPage({ params }: PostPageProps) {
// fetch は revalidate 設定に従ってデータを取得・キャッシュする
const postData: PostData = await getPostData(params.slug);
if (!postData) { // generateStaticParams で指定されていない slug にアクセスした場合のハンドリング
// getPostData が見つからないデータを返した場合に 404 を返すなどのロジックが必要
// 現状の getPostData はファイルが見つからないと例外を投げるため、エラーハンドリングが別途必要
}
return (
{postData.title}
{postData.date}
Back to posts list );
}
``
revalidate` オプションを指定することで、このページは最初のアクセス時には静的に提供され、最後のアクセスから60秒以上経過した後の次のアクセス時に、バックグラウンドで新しいデータがフェッチされ、ページが再生成されます。
これは非常にシンプルな例ですが、Next.jsのルーティング、データ取得(SSG/ISR)、コンポーネントの使用、そしてAPIルート以外のサーバーサイドロジックの記述方法を示しています。
Next.jsのコミュニティとエコシステム
Next.jsは非常に活発なコミュニティを持っています。
- 豊富なドキュメント: 公式ドキュメントは非常に充実しており、導入から高度な機能まで網羅されています。
- 広範なエコシステム: Reactのエコシステム全体(UIライブラリ、状態管理ライブラリ、データ取得ライブラリなど)を利用できることに加え、Next.jsに特化したライブラリやツールも多数存在します。
- 活発な議論: GitHubリポジトリ、Discordサーバー、Stack Overflowなどで、開発者間の情報交換や問題解決が活発に行われています。
- 多数の事例: 小規模な個人ブログから大規模なエンタープライズアプリケーションまで、多くの企業や開発者がNext.jsを採用しており、その成功事例が豊富にあります。
何か問題に直面したり、新しい機能を使いたいと思った際に、多くの情報やサポートを見つけやすいのは、Next.jsを使用する上での大きな安心材料となります。
まとめ:Next.jsが現代Web開発にもたらす価値
この記事では、Next.jsがReactのためのフレームワークであること、その登場背景、そして現代Web開発におけるNext.jsの強力なメリット(開発体験、パフォーマンス、SEO、運用保守、スケーラビリティ)と、利用上の考慮事項について詳細に解説しました。また、App Routerを中心とした基本的な使い方、様々なレンダリング戦略、画像の最適化などの高度な機能、Pages RouterとApp Routerの違い、そして簡単なブログサイトの構築例も示しました。
Next.jsは、単なるReactのルーティングライブラリやビルドツールではありません。それは、現代のWebアプリケーションが直面する様々な課題(パフォーマンス、SEO、開発効率など)を、サーバーコンポーネントやファイルシステムベースルーティング、柔軟なレンダリング戦略といった革新的なアプローチで解決しようとする包括的なフレームワークです。
Next.jsを使うことで、開発者は
- 初期表示が高速でユーザー体験に優れたWebサイトを構築できる。
- 検索エンジンに強く、集客力の高いWebサイトを構築できる。
- 効率的かつ快適にアプリケーションを開発できる。
- 小規模から大規模まで、様々な要件のアプリケーションに対応できる。
- 将来的なWeb技術の進化(特にReact Server Components)にも対応しやすい。
といった多くの恩恵を受けることができます。
もちろん、どの技術を採用するかはプロジェクトの特性やチームの経験によって異なります。しかし、Reactを用いたWebアプリケーション開発を検討しているなら、Next.jsは間違いなく最も有力な選択肢の一つです。
App Routerの登場により、Next.jsはさらに進化し続けています。新しい概念を学ぶ必要はありますが、それに見合うだけの強力な機能と開発体験が待っています。
さあ、あなたもNext.jsの世界に飛び込んで、モダンで高性能なWebアプリケーション開発を始めてみましょう!
参考資料
- Next.js 公式ドキュメント: https://nextjs.org/docs
- Vercel 公式サイト: https://vercel.com/
- React 公式ドキュメント: https://ja.react.dev/ (React Server Componentsについても記載があります)
これらの資料を参考に、さらに深くNext.jsを学び、実践してみてください。