Closed10

next.jsに入門する

ShionShion

セットアップ

App Router vs Pages Router

最新のは App Router で公式もこちらの使用を進めている
App Router はReactの最新機能を使うことができるらしい。Pages Router は昔からあるプロジェクト用に残されていて、基本的にこれから新しくプロジェクトを始める場合は App Router で良さそう。

ディレクトリ構成

ルート直下の構成はこんな感じ。

.
├── README.md
├── app
├── next.config.mjs
├── node_modules
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── tailwind.config.ts
├── tsconfig.json
└── yarn.lock

app配下の構成はこんな感じ。基本はこのディレクトリ直下を触ることになる。

.
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx

layout.tsx

共通のUIコンポーネントを定義する場所。
https://nextjs.org/docs/app/api-reference/file-conventions/layout

page.tsx

App Routerでは、app直下のpage.tsxがルーティングの対象になるみたい。
例えば/aboutを作りたい場合、app/about/page.tsxを作成することになる。
また/blog/1/blog/2のように動的にルーティングする場合、app/blog/[blogId]/page.tsxを作成する。

注意点は、app直下にディレクトリだけを作成した段階ではルーティングされないということ。
page.tsxが作成されて初めてルーティングされる。
https://nextjs.org/docs/app/api-reference/file-conventions/page

ShionShion

サーバーコンポーネントとクライアントコンポーネント

サーバーコンポーネント

  • サーバー側でレンダリングされる
  • デフォルトがサーバーコンポーネント
  • メリット
    • パフォーマンス向上
    • JSのバンドルサイズが小さくなる
    • トークンやAPIキーなど機密情報をクライアントに公開しない
    • 検索エンジンの最適化
  • デメリット
    • useStateやブラウザAPI、ユーザーイベントが使えない
    • 初期ページ以外はCCの方が早い可能性がある

https://nextjs.org/docs/app/building-your-application/rendering/server-components

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

  • 従来の React コンポーネントと同様
  • クライアント側でレンダリングされる
  • ファイル先頭に 'use client' をつけることでクライアントコンポーネントになる

詳細はいかの記事がわかりやすい。
https://zenn.dev/uhyo/articles/react-server-components-multi-stage

使い分け

基本的にNextが推奨するサーバーコンポーネントを使用するようにし、必要に応じてCCを使用するのが良いか。
TIps的に、クライアントコンポーネントはできるだけ小さくするのが良さそう。

Appendix

サーバーコンポーネントで console.log してもコンソール上に表示されないことがわかる。どこに出力されるのかというと、ターミナル上で出力される。これをみるとサーバー側でレンダリングされていることがわかる。

ShionShion

App Router ルーティング概略

基本のルーティング

appディレクトリ以下のpage.tsxがルートページとなる。
appディレクトリ以下にディレクトリを追加し、page.tsxを作成すると、ディレクトリ名がそのままパスになる。

注意点

  • ページコンポーネント名は必ずpage.tsxにする必要がある
  • page.tsx内のルートコンポーネントは default export が必要

動的なルーティング

ディレクトリ名を[id](パラメータ名は任意)とすればOK。
また、idにあたるパラメータはpage.tsxのpropsで取得できる。

const TaskEditIdPage = ({ params }: { params: { id: string } }) => {
  return <div>{params.id}</div>;
};

export default TaskEditIdPage;

ちなみに階層はこんな感じ。

.
└── app
    ├── page.tsx
    └── task
        ├── edit
        │   ├── [id]
        │   │   └── page.tsx
        │   └── page.tsx
        └── page.tsx

パスに反映したくない

appディレクトリ以下にディレクトリを作成するとパスに反映されてしまう。
その場合、ディレクトリを(main}とすればパスに main を反映しないようにできる。

ShionShion

共通レイアウト

layout.tsx で共通レイアウトを定義できる。
layout.tsx はネストして配置可能。
例えば、appディレクトリ以下にlayout.tsxを配置した場合、appディレクトリ以下の全てのpage.tsxに共通のレイアウトが適用される。
また、app/taskディレクトリに layout.tsx を配置すると、app/layout.tsxのレイアウトも反映しつつ、app/task/layout.tsxのサブレイアウトも反映することができる。

$ tree
.
└── app
    ├── favicon.ico
    ├── globals.css
    ├── layout.tsx
    ├── page.tsx
    └── task
        ├── edit
        │   ├── [id]
        │   │   └── page.tsx
        │   └── page.tsx
        ├── layout.tsx
        └── page.tsx

こちらの例。
ルート👇

/task👇

ShionShion

エラーコンポーネント

Not Found

Next.js はデフォルトでNotFoundページが用意されている。
独自でカスタマイズしたい場合は not-found.tsx を作成すれば良い。

Error page

エラーがスローされた時に表示するコンポーネントは error.tsx で定義する。
error.tsx はネストが可能であり、エラーがスローされた際、そのページから最も近い error.tsx が表示される。

ShionShion

ルートハンドラー

APIをNext.js上で直接開発できる機能。
app/api/を作成して定義するやり方が一般的。ファイル名は route.ts とする必要がある。

例えば以下のディレクトリ構成にして、

$ tree
.
└── app
    ├── (admin)
    │   ├── dashboard
    │   │   ├── error.tsx
    │   │   └── page.tsx
    │   └── layout.tsx
    ├── (main)
    │   ├── page.tsx
    │   └── task
    │       ├── edit
    │       │   ├── [id]
    │       │   │   └── page.tsx
    │       │   └── page.tsx
    │       ├── layout.tsx
    │       └── page.tsx
    ├── api
    │   └── tasks
    │       └── route.ts
    ├── cc
    │   └── page.tsx
    ├── error.tsx
    ├── favicon.ico
    ├── globals.css
    ├── layout.tsx
    ├── not-found.tsx
    └── sc
        └── page.tsx

app/api/tasks/route.ts で、次のように定義した時、

import { NextResponse } from "next/server";

export interface Task {
  id: number;
  name: string;
}

const tasks: Task[] = [
  { id: 1, name: "プログラミング" },
  { id: 2, name: "ランニング" },
];

export const GET = async () => {
  return NextResponse.json(
    { tasks },
    {
      status: 200,
    }
  );
};

export const dynamic = "force-dynamic";

http://localhost:3000/api/tasks にアクセスすると結果が返ってくる。

{
   "tasks":[
      {
         "id":1,
         "name":"プログラミング"
      },
      {
         "id":2,
         "name":"ランニング"
      }
   ]
}

ルートハンドラはデフォルトでキャッシュされない。キャッシュ有効かする場合、export const dynamic = 'force-static'とする。

ShionShion

ローディングコンポーネント

ローディングコンポーネントのファイル名は loading.tsx とする。

ShionShion

Server Actions

一般的にボタンクリックでユーザーイベントが発火し、fetch関数などでAPIエンドポイントにアクセスしCRUD処理を行う流れになる。
ServerActionsでは、APIエンドポイントを介さずユーザーイベントの発火で直接CRUD処理が実現できる。
この機能、すごすぎぃ。

ServerActionsを定義するファイルとフォームを定義するコンポーネントファイルは分離するのが良い。

フォームコンポーネント👇

import { createTask } from "@/actions/sampleActions";

const ServerActionsPage = () => {
  return (
    <div>
      <form action={createTask}> // HERE!!
        <input type="text" id="name" name="name" className="bg-gray-200" />
        <button type="submit" className="bg-gray-400 ml-2 px-2">
          送信
        </button>
      </form>
    </div>
  );
};

export default ServerActionsPage;

ServerActions定義ファイル👇

"use server";

export const createTask = async (formData: FormData) => {
  // DBにタスクを作成
  console.log("タスクを作成しました");
  console.log(formData.get("name"));
};

フォームに適当な値を入れて送信してみるとブラウザのコンソールには出力されず、ターミナル上で出力される。これによりサーバー上で実行されていることがわかる。

ShionShion

Middleware

Middlewareを導入することで、ページアクセス前に認証、ロギング、リダイレクトなどを実現できる。
定義するには、src/直下にmiddleware.tsを作成する。

import { NextRequest, NextResponse } from "next/server";

export const middleware = (request: NextRequest) => {
  console.log("ミドルウェア");

  return NextResponse.next();
};

export const config = {
  matcher: ["/", "/task"],
};

トップもしくは/taskにアクセスすると、ターミナル上でミドルウェアと表示されていることがわかる。configはミドルウェアを通過する対象パスを定義したオブジェクト。

このスクラップは2024/08/23にクローズされました