Next.jsを使用したReactアプリケーションのルーティングの基礎ガイド:静的と動的なルーティングの使い方

Next.jsを使用したReactアプリケーションのルーティングの基礎ガイド:静的と動的なルーティングの使い方

本日はNext.jsでのリンク(ルート)処理となります。

前回のNext.js入門は以下となります、是非参照ください。

dev-k.hatenablog.com

Next.jsは、Reactを使用したWebアプリケーション構築における優れたツールの一つです。その最大の利点は、ルーティングの処理を簡単に行えることです。

これまでReactを学習してきた方々にとって、ルーティングを扱うためのライブラリとしては、おそらく「React-Router」や最近では新しい「React Location」など、さまざまな選択肢がありました。

この記事では、Next.jsアプリケーションにおける静的なルーティングと動的なルーティングの基礎について、初心者向けに説明していきます。

プリレンダリング

デフォルトのNext.jsはすべてのページを事前にレンダリングします。

これは、Next.jsがクライアント側のJavaScriptによってすべて行うのではなく、事前に各ページのHTML を生成します。

そのプリレンダリングの2つの方法がございます、SSG(静的生成)とSSR(サーバーサイドレンダリング)です。

Next.jsではSSGおよびSSRは、基本的には組み込み関数を使用して行われる事がほとんどです。

主に、Next.jsにはデータを取得するために使用できる3つの独自の組み込み関数を提供しています。

getStaticProps()

データはビルド時にフェッチされ、クライアントのリクエストの前に利用可能となります。

常にサーバー上で実行され、クライアント上では実行される事はありません。

ページを静的生成(SSG)にできます。

使用可能なすべてのデータフェッチメソッドの中で、GetStaticPropsが最速の読み込み時間を生成するので、データがクライアントに到達する前にレンダリングされるため、ページのSEOが飛躍的に向上します。

getStaticPaths()

データの取り込みは動的API ルートを使用してフェッチされます。

Next.jsはこのgetStaticPathsによって提供されるすべてのパスを静的に事前レンダリングします。

本番環境でのビルド中にのみ実行され、実行時には呼び出される事はありません。

getStaticPathsはgetStaticPropsと一緒に使用されるのが最善となっています。

ですが、getServerSidePropsと使用する事はできませんので注意して下さい。

getServerSideProps()

サーバー側レンダリング(SSR)として、外部データを取得するときに主に使用され、クライアントがページをロードするたびにデータが更新されるため、常に更新された情報を表示できます。

クライアントの要求に応じてサーバーからデータを取得されます。

つまり、getServerSidePropsはリクエストごとに実行されていきます。

この関数ではデータがクライアントに到達する前にレンダリングされるためSEOを改善できます。

Next.jsの事前レンダリングは、サーバー側でHTMLを生成するタイミングによって違うという事になります。

これらの組み込み関数は、必ず使用しなければいけないという事ではございません。

それでは、まずは組み込み関数を使用しない例を見ていきましょう

プロジェクトの準備をお願い致します。

npx create-next-app my-next-app

cd my-next-app

//開発者サーバーを立ち上げる
npm run dev

もし不要なファイルが邪魔で削除しておきたい場合は

rm -r pages/api/
rm public/vercel.svg

Next.js 13.3からの静的エクスポートの変更

Next.js 13.3以降では、静的エクスポートの構成オプションが変更されました。

以前は、next.config.jsファイルでtarget: 'static'を設定することで静的エクスポートを有効にしていましたが、新しいバージョンではoutputプロパティを使用します。

// next.config.js

/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  output: 'export', // 変更
};
 
module.exports = nextConfig;

上記のコードでは、nextConfigという変数にNext.jsの構成オプションを定義しています。

outputプロパティは、エクスポートされる静的ファイルの出力ディレクトリを指定します。上記の例では、'export'という値が設定されているため、静的ファイルは/exportディレクトリに出力されます。

このnextConfigオブジェクトをmodule.exportsでエクスポートすることで、Next.jsアプリケーションでこの構成を使用できます。

なお、このコードを使用する前に、Next.jsのバージョンが13.3以上であることを確認してください。また、他の構成オプションが必要な場合は、nextConfigオブジェクトに追加してカスタマイズすることができます。

また、Next.js 13.3以降の静的エクスポートの変更は、CNA(Client-side rendering, Server-side rendering, and Static Generation)の場合でもnext.config.jsファイルの変更が必要です。

以前のバージョンでは、next.config.jsファイルでtarget: 'static'を設定することで静的エクスポートが有効になっていましたが、Next.js 13.3以降ではoutputプロパティを使用します。

したがって、CNAの場合でもnext.config.jsファイルを変更してoutput: 'export'を設定する必要があります。

この設定により、静的ファイルは/exportディレクトリに出力されます。

カスタムLinkコンポーネント

まずは静的ルーティングとなります。

静的ルーティングではpagesという名前のフォルダがあり、アプリケーションのルーティングはこのフォルダとその中のファイルに完全に関連しています。

Next.jsもLinkコンポーネントがありますが他のインターフェースとは多少異なる部分があります。

ですがほとんどが同様に機能致します。

<a>タグの代わりに使用されるものとなります。

クリック時にページ全体のリダイレクトなしでURLが変更され、ルーティングが新しいページの読み込みと表示を処理してくれます。

ルーティングライブラリコンポーネントをラップします。

つまり「ReactRouter」の標準リンクコンポーネントのように機能させる、カスタムリンクコンポーネントという事になります。

まずはpages/index.jsを開き、index.js内に記述してあるデフォルトのコードを全て削除し以下のように追加して下さい。

// pages/index.js

const Home = () => {

return(

<div>
<h1>Homeページです</h1>
</div>

)
} 

export default Home;

分かりやすくする為にCSSで軽くスタイリングします。

styles/Home.module.css内のデフォルトのコードおよびindex.js同様に全て削除して下さい。

/*styles/Home.module.css */

.title {

background-color: #0000FF;
color: #fff;
font-size: 2em;
text-align: center;

}

/* ルーティング先のスタイル */
.about {
background-color: #FF0000;
color: #fff;
font-size: 2em;
text-align: center;

}

CSSは好みでスタイリングしても構いません。

index.js内でHome.module.cssをインポートします。

// pages/index.js

import styles from '../styles/Home.module.css'

<div>
<h1 className={styles.title}>Homeページです</h1>
</div>

私と同じようにスタイリングしているのであれば、以下のようにブラウザで表示されてるかと思います。

Next.js pages/index.js

それでは、Linkを使用できるようにインポートをします。

すべてのプロジェクトで、Linkという名前の独自のコンポーネントを使用していきます。

// pages/index.js

import Link from 'next/link'

組み込みのNext.jsリンクコンポーネントhref属性を受け入れますが、機能するには<a>タグをその中にネストする必要があります。

// pages/index.js

<Link href="/about/about">
  <a>Aboutへ</a>
</Link>

新しいページのファイル作成について進めていきます。

まず、pagesディレクトリ直下に新しいフォルダを作成します。このフォルダの名前はaboutとします。したがって、フォルダのパスはpages/aboutとなります。

次に、aboutフォルダ内にabout.jsというファイルを作成していきます。このファイルには以下のコードを記述します。

// pages/about/about.js

import styles from '/styles/Home.module.css';
import Link from 'next/link';

const About = () => {
  return (
    <div>
      <h1 className={styles.about}>これはAboutページです</h1>
      <Link href="/"><a>ホームに戻る</a></Link>
    </div>
  );
};

export default About;

Next.js pages/about/about.js

上記のコードでは、stylesモジュールを'/styles/Home.module.css'からインポートしています。また、next/linkモジュールからLinkコンポーネントをインポートしています。

Aboutコンポーネントでは、以下の要素を含んだdiv要素を返しています。

h1要素で「これはAboutページです」というテキストを表示しています。このテキストにはstyles.aboutクラスが適用されます。

Linkコンポーネントでホームページへのリンクを作成しています。リンクのテキストは「ホームに戻る」となっています。

最後に、Aboutコンポーネントをデフォルトエクスポートしています。

保存して更新を行った後、ブラウザで確認してください。

「Aboutへ」というリンクをクリックすると、Aboutページに移動します。

また、「ホームに戻る」リンクをクリックすると、ホームページに戻ります。

このリンクの往復を繰り返して動作を確認してください。

Next.js デモ

aタグではなくボタンタグにしたい場合は簡単です

aタグをbuttonタグに変更してあければ可能となります。

<Link href="/about/about">
  <button>Aboutへ</button>
</Link>

静的ルーティングはサーバー側のレンダリングに似ていますが、リクエストごとにHTMLを生成する代わりに、ビルド時にすべてを事前にコンパイルします。

企業のWebサイトなどの頻繁に変更がないサイトに非常に役立ちます。

このように、pagesフォルダ内のファイル構造は、アプリケーションのルーティング構造を反映しているため、静的ルーティングを簡単に実現することができます。

このアプローチにより、URLパスとページの関連付けを直感的かつ効率的に行うことができます。

動的ルートでの処理

Next.jsはアプリケーションをビルド時にすべてを事前にレンダリングされてユーザーに提供するページを備えたサイトであり「ユーザーが要求する前でもページを利用できます」。

ですが、静的レンダリングを使用すると、複雑なアプリケーションでは、事前定義されたパスを使用してルートを定義するだけでは必ずしも十分とは言えません。

それは、すべての静的レンダリングはブラウザの外部で行われるためなので、唯一の問題点となります。

動的ルーティングでは、クエリパラメータを動的に渡しpagesフォルダを離れることなく動的ページを作成し、サーバー側のレンダリングを使用して動的なWebページ(ユーザーが新しい要求を行うたびに変更されるページ)を自動的に生成しています。

Next.jsのルーティングのアーキテクチャにより、ページの作成とリンクが非常に簡単となっています。

つまり動的ルートはURLからのクエリIDに応じて、コンテンツを動的にレンダリングする特別なルートという事です。

JSONデータ」に基づいてページは、特別なファイル名でページを作成し、2つの機能を実装することによって行われます。

その特別なファイル名には定義規則があります。

それはファイル名やフォルダ名を[ ]でラップしてあげる事となります。

pages/about/[about].js

この特別なファイルによって処理されていきます。

角かっこ[ ]を使用して動的なルートをネストすることも容易となります。

これを行うにはuseRouterフックをインポートして呼び出す必要があります。

「useRouter」はReactフックです。

// pages/about/[about].js
import {useRouter} from 'next/router'

順番にいきましょう

まずはaboutフォルダ名を変更します。

[country]とします。

about.jsは[city].jsに変更します。

pages/[country]/[city].js

index.jsファイルにそれぞれの国の都市の連想配列データを用意します。

先述のコードはそのままでお願いします。

// pages/index.js

const cityList = [
  {
      country: "JPN",
      city: "Tokyo"
  },
  {
      country: "USA",
      city: "NewYork"
  },
  {
      country: "Spain",
      city: "Madrid"
  },
  {
      country: "France",
      city: "Paris"
  }
];

[city].jsファイルに移動し以下のように記述下さい。

// pages/[country]/[city].js

import styles from '/styles/Home.module.css';
import Link  from 'next/link'

import { useRouter } from 'next/router'; 

const About = () => {

    const router = useRouter();

    const { country, city } = router.query;
    
    return(
        <div><h1 className={styles.about}>Aboutページです</h1>
        <Link href="/"><a>Back to Home</a></Link>
        <br />

    { country }の都市は{city}です。 

        </div>
    )
}
export default About;

このファイルでは、stylesモジュールとLinkコンポーネントをインポートし、useRouterフックを使用してルーターオブジェクトを取得しています。

useRouterフックを使うことで、現在のルートのクエリパラメータにアクセスすることができます。

router.queryオブジェクトからcountrycityの値を取得し、それを使用してページの表示内容を動的に変更します。

index.jsファイルに戻りましょう

動的にルートが可能になるように書いていく必要があります。

// pages/index.js

const Home = () => {

  return(
    <div>
    <h1 className={styles.title}>Homeページです</h1>
    <Link href='/about/about'><button>Aboutへ</button></Link>

//ここから
{cityList.map((item, index) => (
<li key={index}>

<Link as={`/${item.country}/${item.city}`} href="/[country]/[city]"><a>
     {item.country}<br />
     {item.city}
</a></Link>

 </li>
    ))}

    </div>
  )
}

export default Home

Link href=の部分を見てもらうとasプロパティに変わってるかと思います。

これは正確なルートの際はasプロパティで渡す必要がある為です。

また、<Link href="/[country]/[city]">hrefは、Next.jsの動的ルーティング機能におけるURLパターンを定義しています。

/[country]/[city]は、2つの動的なパラメータ(countryとcity)を持つパスを表しています。

このように、このパスが表示されるページは、動的ルーティング機能によって、/日本/東京/アメリカ/ニューヨークなど、任意のcountrycityに対応できます。

これにより、一つのページを複数のパスで表示できるようになり、ユーザーが異なるパスをたどったときに、同じコンポーネントが再利用されることができます。

Next.js デモ

ブラウザでご確認できましたら、このコードを必要に応じて修正や調整を行ってください。

動的ルートを拡張して、3つのドット…[ファイル名]で事前定義されたルートは、動的ルートよりも最優先され、これらはすべてのルートをキャッチします。

例えば、以下に、動的ルートを拡張して事前定義されたルートを追加する例を示します。

// pages/[...slug].js

const CustomPage = () => {
  return (
    <div>
      <h1>事前定義されたルート</h1>
      <p>このルートは他のすべてのルートをキャッチします</p>
    </div>
  );
};

export default CustomPage;

上記のコードでは、[...slug].jsというファイルを作成しています。このファイルは、3つのドット...で始まるファイル名になっており、事前定義されたルートを表します。

CustomPageコンポーネント内では、単純なテキストを表示していますが、ここにはカスタムのコンテンツやロジックを追加することができます。

この事前定義されたルートは、他のすべてのルートよりも優先されます。つまり、このルートは他のすべてのルートにマッチし、キャッチすることができます。

上記のコード例を参考に、必要なコンテンツやロジックを追加してください。

では、組み込み関数のgetServerSidePropsメソッドの例を見ていきましょう。

まず、getServerSidePropsメソッドとpages/[country]/[city].jsファイルおよび useRouterフックを使用する方法は、Next.js フレームワークでのデータ取得およびルーティングの異なるアプローチです。

pages/[country]/[city].jsファイルは、Next.jsのダイナミックルーティング機能を利用して、動的なURL パラメータを使用するページを作成するために使用されます。

getServerSidePropsメソッドは、サーバーサイドでのデータフェッチおよびレンダリングを行うために使用されます。このメソッドは、ページの初回アクセス時やリクエストごとに呼び出され、サーバーサイドで実行されるため、動的なデータを取得してページに注入することができます。一般的に、データの取得や外部 API の呼び出しが必要な場合に使用されます。

つまり、getServerSidePropsメソッドはサーバーサイドでのデータ取得とレンダリングに使用され、pages/[country]/[city].jsファイルとuseRouterフックは、クライアントサイドでの動的なルーティングとパラメータの取得に使用されます。

それでは、pages/[country]/[city].jsコードをgetServerSidePropsを使用するように書き換えていきましょう。

// pages/[country]/[city].js

const cityList = [
  {
    country: "JPN",
    city: "Tokyo"
  },
  {
    country: "USA",
    city: "NewYork"
  },
  {
    country: "Spain",
    city: "Madrid"
  },
  {
    country: "France",
    city: "Paris"
  }
];
import styles from '/styles/Home.module.css';
import Link from 'next/link';

const CityPage = ({ country, city }) => {
  return (
    <div>
      {/* Aboutページのタイトル */}
      <h1 className={styles.about}>Aboutページです</h1>
      {/* ホームへのリンク */}
      <Link href="/"><a>Back to Home</a></Link>
      <br />

      {/* 都市情報の表示 */}
      {country}の都市は{city}です。
    </div>
  );
};

export async function getServerSideProps({ params }) {
  // パスパラメータからcountryとcityを取得
  const { country, city } = params;
  
  return {
    props: {
      country,
      city
    }
  };
}

export default CityPage;

上記のコードでは、[country][city]パスパラメータを受け取るCityPageコンポーネントを作成しました。

getServerSideProps関数では、パスパラメータからcountrycityを取得し、Propsとして返しています。

また、Homeコンポーネント側ではcityListを使用してループを行い、各都市のリンクを生成しています。

それぞれのリンクは/[country]/[city]パスにマッピングされています。

このように書き換えることで、正しいgetServerSidePropsを使用してデータを取得し、それをページに表示することができます。

動的ルートを静的ルートにプリレンダリング

Next.jsでは、動的ルーティングを使用すると、動的なパラメータを持つページを簡単に作成できますが、静的なコンテンツを提供するためには、そのページを静的にプリレンダリングする必要があります。

Next.jsでは、getStaticPathsおよびgetStaticPropsメソッドを使用して、動的ルートを静的ルートにプリレンダリングできます。

これらのメソッドを使うことで、静的ファイルとしてHTMLを生成し、そのHTMLを後でサーバーから提供することができます。

では、SSGとして、静的生成する際にpagesフォルダ内のファイル構造は、アプリケーションのルーティング構造を反映しているため、静的ルーティングを簡単に実現することができますが、組み込み関数であるgetStaticPropsメソッドを使用した静的生成との違いはなんでしょうか?

pagesフォルダ内のファイル構造に基づいてルーティングを行う場合、Next.jsでは静的ルーティングが簡単に実現できます。これは、ファイルとフォルダの階層がURLパスと対応しており、ファイル名自体がそのページのルートを表しているためです。このアプローチにより、静的なページの生成とルーティングが自然に結びつきます。

一方、getStaticPropsメソッドを使用した静的生成は、「ビルド時」にデータを取得してページを生成する方法です。

getStaticPropsは、サーバーサイドでのデータ取得や外部APIへのリクエストなど、非同期のデータ取得処理を含めることができます。

このメソッドを使用することで、事前にデータを取得して静的なHTMLファイルを生成し、クライアントへ配信することができます。

主な違いは以下の通りです

ルーティングの自動生成:pagesフォルダ内のファイル構造を利用すると、静的ルーティングが自動的に実現されます。ファイルとフォルダの階層がURLパスと対応しているため、追加のルーティング設定は不要です。一方、getStaticPropsメソッドを使用した静的生成では、ルートごとに明示的なデータ取得処理とルーティング設定が必要です。

データの取得タイミング::pagesフォルダ内のファイル構造を利用する場合、ビルド時に静的なHTMLファイルが生成されます。そのため、データの取得タイミングはビルド時点で決まります。一方、getStaticPropsメソッドを使用した静的生成では、データの取得タイミングをコントロールできます。データの取得はビルド時だけでなく、リクエスト時にも行うことができます。

動的なデータの処理::pagesフォルダ内のファイル構造を利用する場合、静的なルートとデータの組み合わせで動的なコンテンツを実現することも可能ですが、静的なHTMLファイルとして生成されるため、クライアントサイドでの動的なデータの取得や更新は必要な処理となります。

getStaticPropsメソッドを使用することで、動的なデータの取得や処理を行うことができます。例えば、外部APIからデータを取得したり、データベースへのクエリを実行したりすることができます。これにより、動的なコンテンツを生成する際に必要なデータを事前に取得し、静的なHTMLファイルとして生成することができます。

また、getStaticPropsメソッドは、ビルド時に実行されるため、生成される静的ファイルは事前に計算されたデータを持っています。そのため、クライアント側のJavaScriptの実行は不要であり、高速でパフォーマンスの良いページを提供することができます。

一方、pagesフォルダ内のファイル構造を利用した静的ルーティングでは、事前に用意された静的なHTMLファイルが生成されるため、ビルド時のデータの状態が反映されます。データの更新がある場合は、再ビルドが必要となります。

以下のように、getStaticPathsgetStaticPropsメソッドを追加して、すべての都市のページをプリレンダリングが可能です。

import styles from '/styles/Home.module.css';
import Link  from 'next/link'
import { getCities } from '../lib/cities'

const Home = ({ cityList }) => {

  return(
    <div>
    <h1 className={styles.title}>Homeページです</h1>
    <Link href='/about/about'><button>Aboutへ</button></Link>

    {cityList.map((item, index) => (
      <li key={index}>
        <Link href={`/${item.country}/${item.city}`}><a>
          {item.country}<br />
          {item.city}
        </a></Link>
      </li>
    ))}

    </div>
  )
}

export async function getStaticPaths() {
  // すべての静的パスを生成する
  const cityList = await getCities()
  const paths = cityList.map((item) => ({
    params: { country: item.country, city: item.city }
  }))
  return { paths, fallback: false }
}

export async function getStaticProps() {
  // cityListを取得する
  const cityList = await getCities()
  return { props: { cityList } }
}

export default Home

getStaticPathsメソッドでは、getCities関数を使用して、すべての都市のリストを取得し、pathsオブジェクトに都市ごとにパラメータを設定します。

getStaticPropsメソッドでは、同様にgetCities関数を使用して、都市のリストを取得し、Propsとして返します。

これで、すべての都市のページが事前にプリレンダリングされ、静的なHTMLファイルが生成されます。ユーザーが都市のページにアクセスすると、クライアントサイドでJavaScriptを実行する必要がなく、静的なコンテンツが表示されます。

getStaticPropsメソッドを使用した静的生成は、動的なデータの取得や処理を行うための柔軟性と高速なパフォーマンスを提供します。

一方、pagesフォルダ内のファイル構造を利用した静的ルーティングは、ファイルの階層を活用して簡単に静的なルーティングを実現できますが、ビルド時のデータの状態が反映されるため、データの更新には再ビルドが必要です。

どちらの方法を選択するかは、アプリケーションの要件やデータの特性によって異なります。適切な方法を選択することで、効率的でパフォーマンスの高い静的なページを提供することができます。

最後に

Next.jsは複雑な構成を必要とせずに、すぐに使用できる本番用の機能を備えた素晴らしい開発者エクスペリエンスを提供します。

当ブログでの記事でNext.jsで正確なルート処理でルーティングする方法に役に立ってくれたら幸いでございます。

本日は以上となります。

最後まで読んで頂きありがとうございました。

この記事が役に立ったら、ブックマークと共有をしていただけると幸いです。

プライバシーポリシー

© 2023 Icons8 LLC. All rights reserved.

© [deve.K], 2023. React logo is a trademark of Facebook, Inc. JavaScript is a trademark of Oracle Corporation and/or its affiliates. jQuery and the jQuery logo are trademarks of the JS Foundation. TypeScript and the TypeScript logo are trademarks of the Microsoft Corporation. Next.js and the Next.js logo are trademarks of Vercel, Inc. Firebase and the Firebase logo are trademarks of Google LLC. All logos edited by [deve.K].