💡

【イラスト付き】Next.jsの書き方を把握する【要点一気読み】

2024/09/05に公開

はじめに

皆さんこんにちは。
今回はNext.jsのコードの基本的な書き方についてご紹介します。

Next.jsはReactをベースとしたフレームワークであり、基本的にReactと同様に記述可能ですが、Next.js固有のルールなどもあります。

こんな人にオススメ

  • Next.jsの書き方の基本について理解したい
  • Reactをの書き方をすでに理解している

初めて学習する方にも分かるように、丁寧に解説していきます。
すでに利用している方も、是非一度目を通していただけると嬉しいです。

😋 Next.jsのコードの雰囲気を捉えられるようにご紹介します♪

ルーティング(App Router)

まずポイントをチェック

  • App Routerという仕組みを使う
  • appフォルダ内にルーティングに対応するフォルダを配置する
    • フォルダ内にはpage.tsxを配置する
    • フォルダをネストすることでネストしたルーティングも可能
    • フォルダ名を[パラメータ名]にすることで動的パラメータの利用が可能
  • 画面遷移はLinkコンポーネントやrouter.pushメソッド

Next.jsではAppRouterという仕組みを利用しルーティングを行います。これはバージョン13.4から追加された機能です。

App Routerはappフォルダ以下に各ページに対応するフォルダを配置することでルーティングの設定を行います。作成したフォルダ名がURLのパスになります。フォルダをネストすると、ルーティングも同様にネストしたルーティングに設定されます。

🍕 例えば、「src/app/route1」フォルダを用意した場合、「http://localhost:3000/route1」が設定されるURLになります。
🍕 例えば、「src/app/route1/route1-1」フォルダを用意した場合、「http://localhost:3000/route1/route1-1」が設定されるURLになります。

各ルートでの表示内容はpage.tsxで定義します。各フォルダにはpage.tsxを配置し、該当のURLにアクセスした際はpage.tsxの内容が画面に表示されます。

src
└── app
    ├── route1
    │   ├── [id]
    │   │   └── page.tsx   ← http://localhost:3000/route1/[id]
    │   ├── route1-1
    │   │   └── page.tsx   ← http://localhost:3000/route1/route1-1
    │   └── page.tsx       ← http://localhost:3000/route1
    ├── route2
    │   └── page.tsx       ← http://localhost:3000/route2
    ├── layout.tsx
    └── page.tsx           ← http://localhost:3000/
src/app/page.tsx(http
export default function Home() {
  return (
    <div>Home</div>
  )
}
src/app/route1/page.tsx(http
export default function Route1() {
  return (
    <div>Route1</div>
  )
}
src/app/route1/route1-1/page.tsx(http
export default function Route1_1() {
  return (
    <div>Route1_1</div>
  )
}

動的パラメータを利用する場合は、フォルダ名を[パラメータ名]の形式で用意します。

🍕 例えば、「src/app/route1/[id]」フォルダを用意した場合、「http://localhost:3000/route1/999」の999をパラメータとして取得できます。

パラメータの取得はpage.tsxのpropsで行います。propsのparamsプロパティにパラメータが含まれるオブジェクトが渡されます。型アノテーションでパラメータの型を指定しますが、パラメータはstring型です。

src/app/route1/[id]/page.tsx(http
export default function Dynamic(props: { params: { id: string } }) {
    return (
        <div>dynamic{props.params.id}</div>
    )
}

画面遷移はLinkコンポーネントかuseRouterを利用します。基本はLinkコンポーネントを利用し、必要な場合のみuseRouterを使います。

Linkコンポーネントは画面遷移のためのリンクとして機能します。パフォーマンスの最適化としてページのプリフェッチが行われており、リンク先のページをバックグラウンドで読み込でおき、リンク押下時に瞬時にページ遷移します。Linkコンポーネントのhrefプロップスに遷移先のパスを指定します。動的パラメータを渡したい場合はパス中に値を含めます。

useRouterのpushメソッドを使うとプログラム的に遷移することができます。何かの処理結果として画面を遷移させたい場合に利用します。pushメソッドの引数に遷移先のパスを指定します。useRouterはフック関数なのでuse clientを記述します。

src/app/page.tsx
'use client';

import Link from "next/link"
import { useRouter } from "next/navigation";

export default function Home() {
  const id = 99;

  // プログラム的に遷移するためのルートハンドラー
  const router = useRouter();
  const toRoute1 = () => {
    router.push('/route1');
  }

  return (
    <div>
      <div>Home</div>
      <hr></hr>
      {/* 各ページへのLinkコンポーネント */}
      <Link href={'/route1'}>Route1</Link><br />
      <Link href={'/route1/route1-1'}>Route1-1</Link><br />
      <Link href={`/route1/${id}`}>Route1/[id]</Link><br />
      <Link href={'/route2'}>Route2</Link><br />
      <button onClick={toRoute1}>Route1</button>
    </div>
  )
}

😋 ページに対応するフォルダ構成でルーティングの設定をします♪

参考リンク集

レイアウト

まずポイントをチェック

  • ルートレイアウトはアプリ全体に適用される
    • METAデータは直接タグを定義せずMETAデータAPIを利用する
  • ルーティングごとのレイアウトを定義可能(ネストレイアウト)
    • 機能(ルート)ごとに共通のレイアウトを定義可能
  • 類似機能のテンプレートはstateを維持せず、コンポーネントの新しいインスタンスがマウントされるが、レイアウトは再レンダリングされないのでstateを維持する

Next.jsではlayout.tsxファイルを配置することでレイアウトの定義を行うことができます。定義したレイアウトはそのlayout.tsxを配置した階層以下の各ページで共通のレイアウトになります。

src
└── app
    ├── route1
    │   ├── route1-1
    │   │   └── page.tsx
    │   ├── layout.tsx      ← http://localhost:3000/route1 以下のレイアウト
    │   └── page.tsx
    ├── route2
    │   └── page.tsx
    ├── layout.tsx     ← http://localhost:3000/ 以下のレイアウト(ルート)
    └── page.tsx

layout.tsxに定義するコンポーネントはpropsとして、同階層のpage.tsxか下位のlayout.tsxを、childrenという名前で受け取ります。型はReact.ReactNodeです。このchildrenをJSXに配置します。

appフォルダの直下に配置するlayout.tsxはルートレイアウトといい、必ず配置します。ルートレイアウトにはhtmlタグとbodyタグが必須です。META情報などは直接JSXに記述せずMETAデータAPIを使って定義します。

src/app/layout.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
  title: 'sample-title',
  description: 'This is description'
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <body>
        <div style={{ border: 'solid red' }}>
          <h1>src/app/layout.tsxのレイアウト(ルートレイアウト)</h1>
          {children}
        </div>
      </body>
    </html>
  );
}

ルート以下のフォルダにlayout.tsxをネストして配置することができます。レイアウトをネストした場合、上位のレイアウトと組み合わさったレイアウトになります。これにより機能(ルーティング)ごとにレイアウトを共通化することができます。

🍕 例えば、買い物ページのレイアウトを共通化したい場合を考えます。「src/app/shopping/layout.tsx」として作成することで、本の買い物ページの「src/app/shopping/book/page.tsx」や食品の買い物ページの「src/app/shopping/food/page.tsx」で同じレイアウトを適用することができます。
src/app/route1/layout.tsx
export default function Route1Layout({
    children,
  }: {
    children: React.ReactNode;
  }) {
  return (
    <div style={{ border: 'solid blue' }}>
        <h2>src/app/route1/layout.tsxのレイアウト</h2>
        {children} 
    </div>
  )
}

😋 パスごとにレイアウトを定義し、そのパス以下では同じレイアウトが適用されます♪

参考リンク集

エラー対応

まずポイントをチェック

  • error.tsxは、同階層のpage.tsxでエラーが発生した際に表示される
    • layout.tsxで発生したエラーは同階層のerror.tsxでなく直近の親のerror.tsxで捕捉される
  • not-found.tsxは、notFound関数が実行された場合、not-found.tsxを表示する
  • global-error.tsxは、ルートレイアウト(app/layout.tsx)でエラーが発生した際に表示される

Next.jsでページ表示においてエラーが発生した際は、エラーページ用のファイルを用意しておくことでエラーが発生したpage.tsxやlayout.tsxの代わりにエラーページを表示することができます。エラーを扱うファイルは3種類あります。

error.tsx

error.tsxは、予期しないエラーに対応したエラーページを表します。レンダリングの過程で発生したエラーを捕捉するので、throw文で明示的に発生させたエラーもバグやトラブルで引き起こされたエラーも対応することができます。

error.tsxは同階層のpage.tsxで発生したエラーを捕捉し、エラーページを表示します。同階層にerror.tsxが存在しない場合やlayout.tsxでエラーが発生した場合は最寄りの上の階層のerror.tsxが利用されます。

🍕 例えば、route1フォルダ内にerror.tsxがある場合、同階層のpage.tsxのエラーを捕捉します。
src
└── app
    ├── route1
    │   ├── error.tsx    ←これが反応
    │   ├── layout.tsx
    │   └── page.tsx        ←ここでエラーが発生した場合
    ├── error.tsx
    └── page.tsx

page.tsxでエラーが発生したので、同階層のerror.tsxが表示されます。

🍕 例えば、route1-1フォルダのように同階層にerror.tsxがない場合、上の階層のroute1のerror.tsxで捕捉します。
src
└── app
    ├── route1
    │   ├── route1-1
    │   │   └── page.tsx   ←ここでエラーが発生した場合
    │   ├── error.tsx     ←これが反応
    │   ├── layout.tsx
    │   └── page.tsx
    ├── error.tsx
    └── page.tsx

同階層にerror.tsxがなかったので、上の階層のerror.tsxが表示されます。

🍕 例えば、layout.tsxのエラーは同階層のerror.tsxでは捕捉されないので、上の階層のerror.tsxで捕捉します。
src
└── app
    ├── route1
    │   ├── error.tsx
    │   ├── layout.tsx     ←ここでエラーが発生した場合
    │   └── page.tsx
    ├── error.tsx      ←これが反応
    └── page.tsx

layout.tsxでエラーが発生したので、上の階層のerror.tsxが表示されます。

error.tsxはクライアントコンポーネントとして作ります。propsとしてerrorとresetを受け取ります。errorはエラーオブジェクトが代入され、error.messageでエラーメッセージを受け取れます。resetは再レンダリングのための関数が代入されます。

04.error-handle/src/app/error.tsx
'use client'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>app/error.tsx:{error.message}</h2>
      <button onClick={() => reset()}>再レンダリング</button>
    </div>
  )
}

not-found.tsx

not-found.tsxは該当のページが存在しない際や、該当のリソースが取得できない場合に表示するページです。

not-found.tsxは、同階層のpage.tsxでnotFound()関数を呼び出すことで表示されます。同階層にnot-found.tsxが存在しない場合やlayout.tsxでnotFound()関数を実行した場合は最寄りの上の階層のnot-found.tsxが利用されます。

また、デタラメなパスを指定した際は、最上位のnot-found.tsxが表示されるようです。

🍕 例えば、route1フォルダ内にnot-found.tsxがある場合、同階層のpage.tsxのnotFound()を捕捉します。
src
└── app
    ├── route1
    │   ├── error.tsx
    │   ├── layout.tsx
    │   ├── not-found.tsx   ←ここが反応
    │   └── page.tsx      ←ここでnotFound()関数を実行
    ├── error.tsx
    ├── layout.tsx
    ├── not-found.tsx
    └── page.tsx

page.tsxでnotFoundが実行されたので、同階層のnot-found.tsxが表示されます。

🍕 例えば、route1-1フォルダのように同階層にnot-found.tsxがない場合、上の階層のroute1のnot-found.tsxで捕捉します。
src
└── app
    ├── route1
    │   ├── route1-1
    │   │   └── page.tsx    ←ここでnotFound()関数を実行
    │   ├── error.tsx
    │   ├── layout.tsx
    │   ├── not-found.tsx   ←ここが反応
    │   └── page.tsx
    ├── error.tsx
    ├── layout.tsx
    ├── not-found.tsx
    └── page.tsx

同階層にnot-found.tsxがなかったので、上の階層のnot-found.tsxが表示されます。

🍕 例えば、layout.tsxでnotFound()関数を実行した場合は同階層のnot-found.tsxでは捕捉されないので、上の階層のnot-found.tsxで捕捉します。
src
└── app
    ├── route1
    │   ├── route1-1
    │   │   └── page.tsx
    │   ├── error.tsx
    │   ├── layout.tsx    ←ここでnotFound()関数を実行
    │   ├── not-found.tsx
    │   └── page.tsx
    ├── error.tsx
    ├── layout.tsx
    ├── not-found.tsx     ←ここが反応
    └── page.tsx

layout.tsxでnofFoundが実行されたので、上の階層のnot-found.tsxが表示されます。

🍕 例えば、デタラメなパスを指定した際は、最上位のnot-found.tsxが表示されるようです。
src
└── app
    ├── route1
    │   ├── route1-1
    │   │   └── page.tsx
    │   ├── error.tsx
    │   ├── layout.tsx
    │   ├── not-found.tsx
    │   └── page.tsx
    ├── error.tsx
    ├── layout.tsx
    ├── not-found.tsx     ←ここが反応
    └── page.tsx

not-found.tsxはpropsを受け取らないシンプルなコンポーネントです。

04.error-handle/src/app/not-found.tsx
import Link from 'next/link'
 
export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <Link href="/">Return Home</Link>
    </div>
  )
}

global-error.tsx

global-error.tsxはアプリケーション全体を包み込みエラーを処理します。またルート(app直下)でのlayout.tsxのエラーを捕捉することができます。(app/error.tsxでは捕捉できません)

global-error.tsxは本番環境でのみ有効です。開発環境ではエラーオーバーレイが表示されます。

global-error.tsxはクライアントコンポーネントとして用意します。またhtmlとbody要素を配置する必要があります。propsとしてerrorとresetを受け取ります。errorはエラーオブジェクトが代入され、error.messageでエラーメッセージを受け取れます。resetは再レンダリングのための関数が代入されます。

04.error-handle/src/app/global-error.tsx
'use client'
 
export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>global-error.tsx</h2>
        <h3>{error.message}</h3>
      </body>
    </html>
  )
}

😋 エラー用のファイルを配置するだけで、エラー画面が表示されます♪

参考リンク集

スタイル・フォント・画像の適用

まずポイントをチェック

  • スタイル
    • global.css:アプリケーション全体にスタイルを適用できる
    • Tailwind:Tailwindで用意されたクラスを指定することで簡単にスタイルを適用できるCSSフレームワーク
    • CSSモジュール:自作のスタイル定義をクラスとして指定する方法
  • フォント
    • Googleフォントを利用でき、ビルド時にダウンロードされる(追加でリクエストが発生しない)
  • 画像
    • 最適化機能を持つImageコンポーネントを使う

表示に関する内容、スタイルとフォントと画像について紹介します。Next.jsではそれぞれで簡単に利用できる仕組みや最適化の仕組みを持っています。Next.jsではこれらの機能を活用し見た目について設定を行なっていきます。

スタイル

Next.jsではいくつかのスタイルの定義方法が用意されています。その中でも主なものをご紹介します。なおこれらの方法は併用することができます。

global.css

global.cssを利用することでアプリケーション全体にスタイルを適用することができます。リセットCSSや全体の共通的なスタイルなどを指定します。

従来のCSSと同等に扱えますが、全てをglobal.cssに記述するとファイルが肥大化してしまうため、全体のことに絞って利用します。

appフォルダの直下に作成し、app/layout.tsxでインポートすることでアプリ全体に適用されます。

05.style-font/src/app/globals.css
body {
  background-color: silver;
}
05.style-font/src/app/layout.tsx
import "./globals.css"; // global.cssをインポート

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Tailwind

Tailwindはあらかじめ用意されたクラスを指定することで、簡単にスタイルを適用できるCSSフレームワークです。Next.jsではcreate-next-appで雛形を作成する際にTailwindの利用を指定することですぐに利用開始できます。

雛形作成時に利用を指定するとglobal.cssにTailwindのCSSディレクティブが追加されます。このうちの「@tailwind base」はブラウザが持つデフォルトのスタイルをリセットします。

05.style-font/src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  background-color: silver;
}

Tailwindの利用はクラス名を指定するだけなので、あとは必要に応じてlayout.tsxやpage.tsxで利用します。

05.style-font/src/app/route2/layout.tsx(緑の枠線を指定)
export default function Route2Layout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <div>
            <section className="border-green-400 border-4">
                <h1>Route2 Layout</h1>
                {children}
            </section>
        </div>
    );
}
05.style-font/src/app/route2/page.tsx(背景を赤色に指定)
export default function Route2() {
    return (
        <h1 className="bg-red-100 h-5">Route2</h1>
    )
}

CSSモジュール

CSSモジュールは自作のスタイル定義をCSSクラスとして定義しておき、コンポーネント内で指定して利用する方法です。このクラス名はローカルスコープになるので、他のコンポーネントと競合しません。スタイル自体もローカルスコープなのでスタイルの衝突も発生しません。

任意の名前.module.cssファイルを作成し、インポートすることで利用します。JSXでクラス名を指定しスタイルを適用します。

05.style-font/src/app/route1/sytles.module.css(2つのCSSクラスを定義)
.title {
    color: red;
}

.section1 {
    border: solid;
}
05.style-font/src/app/route1/layout.tsx
import styles from './sytles.module.css'; // CSSモジュールをインポート

export default function Route1Layout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <div>
            <section className={styles.section1}> // クラス名を指定して適用
                <h1>Route1 Layout</h1>
                {children}
            </section>
        </div>
    );
}
05.style-font/src/app/route1/page.tsx
import styles from './sytles.module.css'; // CSSモジュールをインポート

export default function Route1() {
    return (
        <h1 className={styles.title}>Route1</h1> // クラス名を指定して適用
    )
}

フォント

Next.jsではGoogleフォントやカスタムフォントを利用できます。ここではGoogleフォントの利用方法について説明します。

Next.jsではフォントを最適に組み込むよう最適化が施されています。フォント情報はビルド時にダウンロードし追加リクエストが発生しないための仕組みなど、様々な工夫によりパフォーマンスを向上させています。最適化の詳細は公式のドキュメントをご確認ください。

Googleフォントを利用するにはnext/font/googleから目的のフォントをインポートします。フォントは可変フォントを選択することで、Next.jsが最適に適用されるよう制御してくれます。

フォント利用時にはサブセットを指定します。これにより必要な文字表現のみのフォントを取得しファイルサイズが削減されます。

ルートレイアウトのbodyタグで「フォント.className」を指定することでアプリケーション全体にフォントの設定を適用することができます。

05.style-font/src/app/layout.tsx(アプリ全体にInterフォントを適用する例)

import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}
05.style-font/src/app/layout.tsx(アプリ全体にRobotoフォントを適用する例)
import { Roboto_Flex } from "next/font/google";

const robot = Roboto_Flex({ subsets: ["latin"] });

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={robot.className}>{children}</body>
    </html>
  );
}

画像の表示

Next.jsでは画像の配置するには、Next.jsが用意した専用のImageコンポーネントを使います。Imageコンポーネントには様々な最適化機能が備わっています。レイアウトのズレ防止や遅延読みこみなど、詳細は公式ドキュメントをご確認ください。

Imageコンポーネントはnext/imageからインポートし、JSXに配置して利用します。srcには画像ファイルのパスか、インポートした画像ファイルを指定します。

srcに画像ファイルのパスを指定する場合はwidthとheightの指定も必要です。インポートした画像ファイルを指定する場合は、インポートした画像に基づいてwidthとheightを自動で設定してくれるので指定は不要です。

05.style-font/src/app/page.tsx
import dogPic from "../../public/dog-pic.jpg"; // 画像ファイルをインポート
import Image from "next/image";

export default function Home() {
  return (
    <div>
        <Image src={dogPic} alt=""></Image> // インポーした画像ファイルを指定
        <Image src="/dog-pic.jpg" alt="" width={1920} height={1279}></Image> // パスを指定
    </div>
  );
}

😋 Next.jsはスタイルやフォント、画像の指定においての仕組みを提供しています♪

参考リンク集

おわりに

皆さん、お疲れ様でした。
ここまでご覧いただき、ありがとうございました。

Next.jsの基本的な書き方について確認をしていただきました。
Reactの書き方を基本としつつフォルダ構成やファイル名のルールなど独自のルールがあります。

😋 これからもプログラミング学習頑張りましょう♪

サンプルコード

Discussion