Next.jsのApp Routerチュートリアルで説明されていること


## 記事概要
以下のチュートリアルの各章の概要と出てくる機能や説明していることのまとめ。App Routerの機能を改めて見つめたい人。

https://nextjs.org/learn/dashboard-app

Vercelにデプロイする前提だが、DBをdockerで用意してローカルで行ったのでVercelの登録が面倒な人にも参考になるかも。

## 1. Getting Started
サンプルアプリをインストールする。特筆すべき点なし。サンプルアプリに機能追加をしていく、というチュートリアル

## 2. CSS Styling
- CSSプロパティの設定にtailwindcssを使っている。
決められた値を組み合わせて簡単にスタイリングできるというライブラリ。
例えば`<div className="flex p-4 md:h-52">`とした場合
    - `display:flex`
    - `padding: 1rem`
    - ウィンドウサイズがmedium以上の場合は`height: 13rem`
という意味になる。
※0.25remが基本単位らしい。一応0.25remが基本単位というのはここが根拠かも？https://tailwindcss.com/docs/theme
 > --spacing: 0.25rem;
 ...
 --spacing-*	Spacing and sizing utilities like px-4, max-h-16, and many more

 - CSSスタイリングの条件分岐はclsx
 statusがXXならYYというスタイルを適用する、というわかりやすいライブラリ。

```typescript
export default function InvoiceStatus({ status }: { status: string }) {
  return (
    <span
      className={clsx(
        'inline-flex items-center rounded-full px-2 py-1 text-sm',
        {
          'bg-gray-100 text-gray-500': status === 'pending',
          'bg-green-500 text-white': status === 'paid',
        },
      )}
    >
```

## 3. Optimizing Fonts and Images
フォントを変えたり画像をレスポンシブにしている。
画像についてはtailwindcssの書き方で、以下はモバイルサイズのウィンドウだとhidden, mediumサイズ以上だとblockとして表示されるように設定している。

```react
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop version"
      />
```

## 4. Creating Layouts and Pages
App Routerでダミーページを作っておき、`layout.tsx`でサイドバーを表示するのがメイン。
`layout.tsx`を配置すると、その配下の`page.tsx`全部に影響する。
`layout.tsx`はchildrenを必須引数としてとり、これは配下のページを指す。

https://nextjsjp.org/docs/app/api-reference/file-conventions/layout

## 5. Navigating Between Pages
サイドバーにある、各ページへのリンクを表示するNavLinksコンポーネントを作る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490290/34ab37bc-fe36-47dd-aa94-2c34fc39e311.png)

- `<Link>`は`<a>`タグと同じだがbackgroundでprefecthしてくれていたり色々やってくれている
- `usePathname`で現在のURLのパスを取得できる
- サイドバーの現在表示しているページだけハイライトするのもclsxでやっている
```react
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
 
// ...
//※linksはハードコードでした
 
export default function NavLinks() {
  const pathname = usePathname();
 
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className={clsx(
              'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
              {
                'bg-sky-100 text-blue-600': pathname === link.href,
              },
            )}
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}
```

## 6. Setting Up Your Database

DBを使うためにVercelへデプロイしようという内容。
ローカルでやりたかったのでdockerでDBを起動した。
### おまけ〜ローカル環境での工夫
- bcryptは環境の問題で動かなかったのでbycryptjsをインストールしてそれを使った。import文を変えるのみでコードは変えないでも動いた。
- dockerでpostgres
`.env`ファイルの書き方がvercelのものしか書いておらず困ったが、以下でいけた。ユーザー名とDB名が'postgres'の場合

```
POSTGRES_URL="postgres://postgres:password@localhost:5432/postgres"
POSTGRES_PRISMA_URL="postgres://postgres:password@localhost:5432/postgres"
POSTGRES_URL_NON_POOLING="postgres://postgres:password@localhost:5432/postgres"
POSTGRES_USER="postgres"
POSTGRES_HOST="localhost"
POSTGRES_PASSWORD="password"
POSTGRES_DATABASE="postgres"
```
以降でDB接続する箇所はTLSを使わないので、全て以下のようにsslオプションを削除して使うことになる。

```diff_typescript
- const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' });
+ const sql = postgres(process.env.POSTGRES_URL!);
```

## 7. Fetching Data

- DBからデータをfetchするにはいくつかの方法がある
    1. [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)を使ってAPIを作る。
        - クライアントコードからクエリする場合にはセキュリティ上必要になる。
        - `app/api/route.ts`に`export async function GET(request: Request) {}`とかのメソッドを用意するだけ。
        - 実はChapter6でDBにデータをseedするときにさらっと使った。
    2. サーバーコンポーネントを使う。チュートリアルではこの方法。

- request waterfallsにならないように並列でリクエストしよう
以下みたいな順次処理を`promise.all()`を使って書こう、というような内容。
しかしこれだと一つのリクエストだけ遅かったら全部処理止まるけどどうする？という疑問提起をして次のChapterに続く。
```typescript
const revenue = await fetchRevenue();
const latestInvoices = await fetchLatestInvoices(); // wait for fetchRevenue() to finish
const {
  numberOfInvoices,
  numberOfCustomers,
  totalPaidInvoices,
  totalPendingInvoices,
} = await fetchCardData(); // wait for fetchLatestInvoices() to finish
```

## 8. Static and Dynamic Rendering
- Static RenderingとDynamic Rendering
最近のNext.jsではSSR, SSGという言葉ではなくSSG, ISRあたりをDynamic, SSRをStaticと表現しているらしい。
- Dynamic Renderingでは遅いリクエストがあると表示が遅い。
前Chapterからの続き。これをどう解消するか？

## 9. Streaming
読み込みが完了するまで別のページやコンポーネントを表示することができる機能。
- page単位では`loading.tsx`ファイルを用意する
ページのレンダリングが完了するまでは`loading.tsx`ファイルの内容が表示され、レンダリングが終わると自動で本来のページに切り替わる。

`loading.tsx`は配下のページ全てに影響してしまうため、Route Groupsを使って影響範囲を`/dashboard/page.tsx`だけに変更する。
以下のように(overview)を作成するとこの部分はアクセス時のパスに影響しないため、`/dashboard`にアクセスすれば変わらず`/dashboard/page.tsx`が表示されるまま影響範囲を分離することができる。

```diff_shell
 app/
- └── dashboard/
-     ├── page.tsx
-     ├── loading.tsx
      ├── Invoices/
      │   └── page.tsx
      └── Customers/
          └── page.tsx

  app/
+ └── dashboard/
+     ├── (overview)/
+     │   ├── page.tsx
+     │   └── loading.tsx
      ├── Invoices/
      │   └── page.tsx
      └── Customers/
         └── page.tsx

```
- コンポーネント単位ではReactの`<Suspense>`機能を利用する
時間のかかる処理をコンポーネント(以下では`RevenueChart`)内の処理に含め、処理待ち中に表示したいコンポーネントをfallbackに指定する。コンポーネントの処理が終わったら非同期に本来のコンポーネントに切り替わってくれる。

```typescript
import { Suspense } from 'react';
import { RevenueChartSkeleton } from '@/app/ui/skeletons';

//...
 return (
// ...
        <Suspense fallback={<RevenueChartSkeleton />}>
          <RevenueChart />
        </Suspense>
```

## 10. Partial Prerendering
Partial PrerenderingはNext15の時点でもまだexperimentalな機能だが、これからデフォルトのレンダリングモデルになっていくと思っているとのこと。
設定ファイルで有効化するだけで、必要な場合に自動で実施されてくれるようになる。

### `<Suspense>`単体とPartial Prerenderingの違い
`<Suspense>`をすることでそのコンポーネントだけ非同期的に後から表示させられるが、これは"リクエスト時に"ページの静的な部分のレンダリングが行われている。
一方Partial Prerenderingを有効にすると、`<Suspense>`で囲ったコンポーネントだけが欠けた(holeとした)静的ページを"ビルド時に"作成するようだ。
イメージ的にはSSR+fallbackか、SSG+fallbackかという感じか。
公式の説明も動画かどこかにあるのかな？という気がするが、誰かが答えてくれているdiscussionがあったのでそちらを貼る。

https://github.com/vercel/next.js/discussions/58322

## 11. Adding Search and Pagination
検索用のコンポーネントとページネーション用のコンポーネントを作成する。URLのクエリパラメータを使ってページやコンポーネント間でのパラメータをやりとりしているため、そのための手段が多数使われている。

### next/navigation
`<Link>`以外で以下の3つの機能が登場する
- `useSearchParams`- 現在のURLからパラメータを取得できる。 `/dashboard/invoices?page=1&query=pending`なら`{page: '1', query: 'pending'}`
- `usePathname` - 現在のURLからパスが取得できる。 `/dashboard/invoices`なら `/dashboard/invoices`
- `useRouter` - [URL操作を行うメソッドたち](https://nextjs.org/docs/app/api-reference/functions/use-router#userouter)を返す。今回は遷移する`replace`しか使わないが、`push`や`back`, `refresh`なども使える。


### Debounce
以下だと入力するたびにハンドラが走って鬱陶しい。これを一定期間止まった場合にのみイベントが走るようにすること。
```typescript
  onChange={(e) => {
    handleSearch(e.target.value);
  }}
```

ライブラリを使うと簡単。
具体的なコードは次のSearchコンポーネントを参照。

### コード例〜Searchコンポーネント

next/navigationのメソッドたちとDebounceは`Search`コンポーネントで使われている。

```typescript
// 入力された値termをクエリパラメータに足したURLに遷移する
'use client';
 
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useDebouncedCallback } from 'use-debounce';
 
export default function Search() {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const { replace } = useRouter();

 // 0.3秒入力がない場合にのみイベントが発生するようにdebounceするライブラリでラップ
  const handleSearch = useDebouncedCallback((term) => {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }, 300);

  .....

{/* URLとsyncするようにdefaultValueを設定 */}
  <input
  className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
  placeholder={placeholder}
  onChange={(e) => {
    handleSearch(e.target.value);
  }}
  defaultValue={searchParams.get('query')?.toString()}
/>

...
}
```


### searchParams
`Page.tsx`の予約されたパラメータとして`searchParams`がある

https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional

`/dashboard/invoices/page.tsx`で以下のように使われ、URLパラメータを取得してTableコンポーネントに渡している。

```typescript
export default async function Page(props: {
  searchParams?: Promise<{
    query?: string;
    page?: string;
  }>;
}) {
  const searchParams = await props.searchParams;
  const query = searchParams?.query || '';
  const currentPage = Number(searchParams?.page) || 1;
...

      <Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
        <Table query={query} currentPage={currentPage} />
      </Suspense>
...
```

#### searchParamsとuseSearchParamsの違い
先述のSearchコンポーネントではnext/navigationの`useSearchParams`を使ってURLパラメータを取得した。
クライアントコンポーネントだとフックを使って`useSearchParams`を使い、サーバーコンポーネントの場合は`searchParams`を使うようだ。
> \<Search\> is a Client Component, so you used the useSearchParams() hook to access the params from the client.
> \<Table\> is a Server Component that fetches its own data, so you can pass the searchParams prop from the page to the component.
As a general rule, if you want to read the params from the client, use the useSearchParams() hook as this avoids having to go back to the server.

### Pagenation
完成系ではないが、`useSearchParams`から現在のページを取得したらあとは以下のように地道に計算したり分岐して表示している。
https://github.com/vercel/next-learn/blob/main/dashboard/starter-example/app/ui/invoices/pagination.tsx

## 12. Mutating Data

### Server Actions
formから直接指定できてサーバーサイドで実行されるメソッド。

```typescript
// Server Component
export default function Page() {
  // Action
  async function create(formData: FormData) {
    'use server';
 
    // Logic to mutate data...
  }
 
  // Invoke the action using the "action" attribute
  return <form action={create}>...</form>;
}
```
メリットの一つとしてprogressive enhancementが挙げられている。
JavaScriptをクライアント側で読み込む必要がないため、formを含んだhtmlだけ最低限の機能としてまず提供できる、ということのよう。
> An advantage of invoking a Server Action within a Server Component is progressive enhancement - forms work even if JavaScript has not yet loaded on the client. For example, without slower internet connections.

なお、POSTエンドポイントが自動で作られてくれているため、自分でAPIエンドポイントを作る必要もない。
> Behind the scenes, Server Actions create a POST API endpoint


### 型チェックライブラリ zod
コンパイル時のみではなく、実行時にも型チェックが行える。外部APIを呼び出す時とかは一応やっておくとよさそう。
```typescript
'use server';
 
import { z } from 'zod';
 
const FormSchema = z.object({
  id: z.string(),
  customerId: z.string(),
  amount: z.coerce.number(),
  status: z.enum(['pending', 'paid']),
  date: z.string(),
});
 
const CreateInvoice = FormSchema.omit({ id: true, date: true });
 
export async function createInvoice(formData: FormData) {
  const { customerId, amount, status } = CreateInvoice.parse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  });
  // ...
}
```

あとは動的ルート(`/dashboard/invoices/[id]/edit/page.tsx`みたいなパス)と、データ更新したらキャッシュをrevalidateしましょう、ということを言っていた。

### bind
Server Actionにform以外の変数を渡したいときはbindする。
`/app/ui/invoices/edit-form.tsx`で、formの入力値にないidをupdateInvoiceアクションに渡す場合には以下のように書く。
```typescript
  import { updateInvoice } from '@/app/lib/actions';
  
  const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);
  return <form action={updateInvoiceWithId}>{/* ... */}</form>;
```
`/app/lib/actions.ts`のServer Actionでは以下のように受け取れる。
```typescript
export async function updateInvoice(id: string, formData: FormData) {
...
```

## 13. Handling Errors
`error.tsx`: Server Actionでuncaught exceptionが発生した場合に備えて、エラーページを用意できる。
`not-found.tsx`: 404エラーを出したいタイミングで`notFound();`すると表示されるページ

## 14. Improving Accessibility
アクセシビリティの話。
適切なHTMLタグを使ったり、altタグによる説明をしたりするとSEO上がったり、スクリーンリーダーで(画像が見えないユーザーに対しても)読み上げてもらえる、と言った点を説明している。
そこから入力値のvalidationやErrorなど、ユーザー体験的な話になっていく。

- eslint-plugin-jsx-a11yというプラグインを使うとアクセシビリティの観点からlintしてくれる(適切なaltプロパティがない、とか)。
- Server Actionでzodの型チェックのエラー時にカスタムメッセージを追加
- `useActionState`を使ったエラーメッセージの表示。
`useActionState`を使うと、state, isLoading(いずれも変数名は任意)を管理できる。クライアントコンポーネント側で以下のようなことが手軽に行える新しい機能のよう。
    - Server Actionの結果(成功/失敗)をstateにすることでエラーハンドリング(stateで条件分岐してエラーメッセージの表示)
    - (チュートリアルでは使っていないが)処理中であることを示すisLoadingによって表示コンテンツを変える
`useActionState`は[公式の例](https://nextjs.org/docs/app/getting-started/error-handling)がわかりやすいかも。

## 15. Adding Authentication
- next-authライブラリを使ったログインフォーム作成
- `middleware.ts`で定義したパスにアクセスしたときに`auth.config.ts`のcallbacksで定義した認可チェックが行われ、未ログインだった場合は`auth.config.ts`のpagesで定義した/loginページにリダイレクトする。

- next-authではsignOut, SignInなどは`use server`で行う必要がある。クライアント側で使用したい場合は[next-auth/reactを使う必要がある](https://authjs.dev/getting-started/session-management/login?framework=Next.js)。
ただし、ログインなどの処理はサーバー側でやる方がセキュアで推奨と公式に書いてある。
https://nextjs.org/docs/app/guides/authentication#auth-js-server-actions

> Since Server Actions always execute on the server, they provide a secure environment for handling authentication logic.

### use clientとuse server
use clientとuse serverは対になるものではない。

use client: クライアントで動作する
use server: サーバーサイドの関数をクライアントコードから呼び出せるようにマークする。server actionsを作るときによく使う。
無印: サーバーで動作するコンポーネントになる。use serverとは違う