【随時更新】Next.js 13入門メモ

はじめに

Next.jsに入門する自分用のメモを本記事に記していきます。
Next.js 13で追加された目玉機能のappディレクトリは2022/12/11現在beta版ですが、今後の正式リリースも見据えて使っていきます。
公式ドキュメントを閲覧しながら独自の解釈で書いているので、誤り等あればご指摘いただけるとありがたいです。

改訂歴

  • 2022/12/11 初版投稿

前提条件

開発環境はmacOS上でDockerコンテナ上に作成します。各ソフトウェアのバージョンは以下のとおりです。メジャーバージョンのみ固定して、マイナーバージョン以降はその時の最新のものを使用しています。

  • Node.js 18
  • Next.js 13

エディタはVS Codeを使用します。

アプリケーション作成

一時的なNode.jsコンテナを起動して、マウントしたカレントディレクトリ配下にNext.jsのアプリケーションのディレクトリを作成します。実行例ではnextjs-appという名前のプロジェクト名にしています。
create-next-appには以下のオプションを指定します。

  • --experimental-app: Next.js 13で追加されたappディレクトリを作成
  • --eslint: ESLintの設定を加える
  • --ts: TypeScriptプロジェクトとして初期化
% docker run --rm -it 
  -v ${PWD}:/app:delegated 
  -w /app 
  node:18-slim 
  sh -c "npm install -g npm@latest && 
         npx -y create-next-app@^13.0.0 nextjs-app \
             --experimental-app --eslint --ts"

開発環境構築

VS Code拡張

コンテナ環境構築に役立つVS Code拡張をインストールします。

% code --install-extension ms-azuretools.vscode-docker
% code --install-extension ms-vscode-remote.remote-containers

コンテナ環境

作成したnextjs-appディレクトリをVS Codeで開きます。

% code nextjs-app

環境構築中にローカル環境に影響を及ぼさないように、開発環境はコンテナに展開します。コンテナ内でNodeモジュールを永続化できるようにnode_modulesディレクトリを削除しておきます。

次に、以下のファイルをアプリケーションディレクトリの直下に作成します。

  • Dockerfile
  • compose.yml
  • .dockerignore

Dockerfile

FROM node:18-slim
ENV NODE_ENV=development \
    WATCHPACK_POLLING=true
# 作業ディレクトリを変更
WORKDIR /app
# 依存パッケージインストール
COPY package*.json ./
RUN npm install
# アプリケーションコード全体をコピー
COPY . .
# 3000ポートを公開
EXPOSE 3000
# コンテナ実行時に組み込みサーバを起動
CMD [ "npm", "run", "dev" ]

.dockerignore

Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

compose.yml

services:
  app:
    container_name: app
    # カレントディレクトリのDockerfileからコンテナイメージをビルドして使用
    build: .
    ports:
      # ホストの3000ポートをコンテナの3000ポートにマッピング
      - 3000:3000
    volumes:
      # node_modulesがホストのカレントディレクトリに共有されないようボリュームに切り出し
      - node_modules:/app/node_modules
      # ホストのカレントディレクトリをコンテナの/appにバインドマウントして共有
      - ./:/app:delegated
volumes:
  node_modules: {}

Dev Containers

VS Code拡張機能のDev Containersを使用して、開発環境コンテナ内でVS Code Serverを実行できるようにします。
.devcontainerディレクトリを作成し、その配下に下記内容のdevcontainer.jsonを作成します。

{
    "name": "Next.js App",
    // コンテナ起動に使用するDocker Composeファイル
    "dockerComposeFile": "../compose.yml",
    // Dev containerとして起動するDocker Composeサービス
    "service": "app",
    // ワークスペースディレクトリ
    "workspaceFolder": "/app",
    // インストールする拡張機能
    "extensions": [
        "ms-ceintl.vscode-language-pack-ja",
        "dsznajder.es7-react-js-snippets",
        "pulkitgangwar.nextjs-snippets"
    ],
    // コンテナ作成後にGitをインストール
    "postCreateCommand": "apt-get update && apt-get install -y git"
}

コマンドパレットからDev Containers: Reopen in Containerを入力して、Dev Containerを起動します。

画面プレビュー

Dev Containerが起動すると、Dockerfileで定義したnpm run devスクリプトが実行され開発サーバが起動します。
VS Codeのターミナルを開き、ポートタブに存在する3000ポートを右クリックしエディターでのプレビューを選択するとVS Code上でアプリケーションの画面が確認できます。
アプリケーションコードを変更すると自動的に画面に反映されるので、この画面プレビューを確認しながらコーディングを行います。

npmスクリプト

開発向け機能をnpm run <スクリプト>で実行します。以下のスクリプトが使用できます。開発中は開発サーバを起動しておくと、変更したコードを自動的に読み込んでくれます。

  • dev: 開発サーバを起動
  • build: 本番アプリケーションをビルド
  • start: 本番サーバを起動
  • lint: ESLintを実行

Routing

appディレクトリ

  • Next.js 13から導入された新しいファイルシステムベースルーターのディレクトリ
  • 従来のルーティングはpagesディレクトリと併用可能(同じURLパスは使えないので注意)
  • appディレクトリ内のコンポーネントはサーバーコンポーネントとクライアントコンポーネントが使える

フォルダとファイル

  • フォルダ名とURLのパスは関連付いている
  • appは/、その配下のフォルダ構造はそのまま階層順にURLのパスになる
  • フォルダ構成がapp/hoge/page.tsx であれば、URLパスは/hogeとなる
    • フォルダ構成: app/hoge/page.tsx
    • URLパス: /hoge
  • フォルダ内にpageファイルが無い場合はフォルダに対応するURLパスにはアクセス不可
  • フォルダ名に()で囲むと配下ファイルをグルーピングできる。()で囲んだフォルダ自体はURLパスから除外される
    • フォルダ構成: app/(hoge)/fuga/page.tsx
    • URLパス: /fuga
  • 各グループ直下にlayoutファイルを置くと、グループごとに異なるレイアウトを適用できる

動的ルート

  • URLパスにパラメータを含むルート
  • pageファイル名を[param]とすることでparamでパラメータを受け取れる
    • フォルダ構成: app/hoge/[id].tsx
    • URLパス: /hoge/:id
    • コンポーネントからの参照方法: const id = useRouter().query.id
  • フォルダをapp/blog/[slug]/page.tsxのように表現することも可能

特殊ファイル

  • フォルダ内には、該当ルートに対する役割を持つ特殊ファイルを配置
  • 必須ファイル
    • page.tsx: 該当ルート固有のUI定義
    • layout.tsx: 複数ページ共有のUI定義。子ページや別レイアウトで使える
  • オプションファイル
    • loading.tsx: ローディングUI定義。React Suspense Boundaryの中でpageや子layoutをラップする
    • error.tsx: エラー処理。React Error Boundaryの中でpageや子layoutをラップする
    • template.tsx: layoutに似ているが、新しいコンポーネントがマウントされたときに使用。ステートは共有されない
    • head.tsx: 該当ルートの<head>タグの内容定義
    • not-fount.tsx: 該当ルートがnotFoundだったときのUI定義
  • 特殊ファイルの拡張子はjsjsxtsxが使用可能

コロケーション

  • コンポーネント、テスト、スタイルシートなどを、関係するルートのフォルダに含めることができる

リンクとナビゲーション

  • appディレクトリでは、サーバーコンポーネントとデータフェッチを同列にするためにServer-Centricルーティングが使われる
  • Server-Centricルーティング
    • クライアントはルートマップをダウンロードせずにルートを覚えておける
  • クライアントサイドナビゲーション
    • Linkコンポーネントを使うことでページ間の遷移でページの再読み込みが発生しなくなる
    • ルート上のセグメントが変更された場合のみ変更される

コンポーネント

  • Reactのコンポーネント
  • Next.jsでは基本的にこれを使用する
  • プリフェッチとルート間のクライアントサイドナビゲーションを提供する
  • 動的ルートをリンクする場合はテンプレートリテラル```を使用する
<Link href={`/blog/${post.slug}`}>

useRouterフック

  • クライアントコンポーネントの中でプログラム的にルートを変更する
  • onClickなど任意の場所でリンクを実行したい場合に使用する

レンダリング

  • レンダリングが行われる場所はクライアント(Webブラウザ)とサーバの2箇所
  • コンポーネント単位、およびルート単位でレンダリング方式を選択できる

コンポーネント

  • Reactが提供する画面部品
  • React 18以降ではコンポーネントレベルでレンダリング場所を選択できる
  • appディレクトリ内のページではデフォルトでサーバコンポーネントが使われる

サーバコンポーネント

  • 正式にはReact Server Components(RSC)
  • React要素ツリー内の一部のコンポーネントだけサーバサイドでレンダリングする仕組み
  • Next.jsのSSRとは異なる機能
  • データベースアクセスや巨大なモジュールのロードだけサーバサイドに移せる

クライアントコンポーネント

  • 従来のReactコンポーネント
  • クライアントサイドでHTMLとJavaScriptを取得してからレンダリングを行う
  • イベントハンドリングやクライアントサイドのフックを使う場合はクライアントコンポーネントを選択する
    • onClick
    • onChange
    • useState
    • useEffect
    • useReduce
  • クライアントコンポーネントはimport文より前の行に'use client'ディレクティブを付ける
'use client';

import { useEffect } from 'react';

export default function Counter() {
  useEffect(async () => {
    // 非同期処理
    });

  return (
    <div>
      <p>hello</p>
    </div>
  );
}

コンポーネントの入れ子

  • サーバコンポーネントの子にクライアントコンポーネントを包含することもできる
  • クライアントコンポーネントの中にサーバコンポーネントを組み込むときは、クライアントコンポーネントを呼び出す際にpropsにサーバコンポーネントを渡す

静的レンダリング

  • Next.jsのデフォルトレンダリング方式
  • CDNから不特定多数に配信するパブリックデータ用途
  • データをキャッシュし、動的関数を含まない場合のみSSGを使用できる
  • ビルド時にレンダリングされる
  • レンダリング結果はキャッシュとしてレスポンスに使用される

コンポーネント種別ごとのレンダリング動作

  • クライアントコンポーネント
    • サーバサイドでレンダリングされたハイドレーション用のHTMLとJSON
  • サーバコンポーネント
    • サーバサイドでレンダリングされたHTML

静的データ取得

  • デフォルトでは、静的レンダリング時にfetch関数の実行結果がキャッシュする
  • fetch関数にrevalidateオプションを付けると再検証時に再レンダリングする

動的レンダリング

  • 他者と共有できないプライベートデータ用途
  • リクエストごとにサーバサイドでレンダリングされる
  • レスポンスはキャッシュされない
  • 静的レンダリング中、動的関数やno cachingオプションの付いたfetch関数を検知すると動的レンダリングモードに切り替わる
  • 1ページの中にパブリックデータとプライベートデータが混在するときに使用
  • CSRのコンポーネントのみクライアント側で後から書き換える
  • Next.jsを使わない素のReactもこの方式

動的関数

  • cookies関数やaheaders関数。

SSG(Static Site Generation)

SSR(Server Side Rendering)

CSR(Client Side Rendering)

レンダリング方式の選択手順

外部への公開有無、実装コストや画面に含まれるデータの性質を考慮して選択。

  • SEOやOGPが必要
    • プライベート/リアルタイムなデータを含む → SSR
    • プライベート/リアルタイムなデータを含まない → SSG
  • SEOやOGPが不要 → CSR

データ取得

  • pageファイルにはデータ取得関数を定義できる
  • ページ内に必要なデータを事前に取得する

サーバサイドのデータ取得

静的生成

getStaticProps
  • SSGで必要なデータをビルド時に取得
  • ISRで必要なデータをオンデマンドに取得
  • この関数があるとNext.jsはSSG対象ページと判定する
getStaticPaths
  • getStaticPropsとセットで使用する
  • 動的ルートを参照する

動的生成

getServerSideProps
  • SSRで必要なデータをリクエストごとに取得
  • ctx引数でリクエスト内容を参照できる
  • { props: { value1, value2... } }の形式で戻り値を定義する。propsはコンポーネント関数に渡される
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) =>
{
  const date = new Date().toString();
  return { props: { date } };
}

const Page: NextPage<Props> = (props) => {
  return (
    <div>
      <h1>now</h1>
      <p>{props.date}</p>    
    </div>       
  );
}   

export default Page;         

Caching

Optimizations

Transpilation

API

  • APIはpages/apiの配下にファイルを用意する
  • 引数reqからリクエスト情報を取得する
  • 引数resでレスポンス情報を操作する
  • リクエストメソッド別の処理はswitchで切り替える

pages/api/messages.tsにチャットメッセージのAPIを作ってみる。

type Data = { user: string } | { err: string }

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
    switch (req.method) {
      case "GET":
        // 固定値のJSONを返してみる
        res.status(200).json([{
            name: "太郎",
            text: "こんにちは!",
        }]);
        break;
      case "POST":
        // リクエストボディから値を取り出す
        const {name, dext} = req.body;
        // ビジネスロジック
        // ...
        // DBへデータ登録
        // ...
        // 結果を返却
        res.status(201).json(message);
        break;
      default:
        return res.status(405).json({ err: 'Method Not Allowed'});
    }
}

Tooling

参考文献

Getting Started
React Server Componentsの仕組み:詳細ガイド

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です