【随時更新】Next.js 13入門メモ
Contents
はじめに
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定義
- 特殊ファイルの拡張子は
js
、jsx
、tsx
が使用可能
コロケーション
- コンポーネント、テスト、スタイルシートなどを、関係するルートのフォルダに含めることができる
リンクとナビゲーション
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'});
}
}