n_itsuki / Next.jsのチュートリアル感想戦

Created Sun, 18 Aug 2024 00:00:00 +0000
4287 Words

Learn Next.jsをやってみてのメモ

後半にすすむにつれ、意味が分からなくなり、雑になっていった…

特にCh13以降(エラー処理・フォームバリデーション・認証と認可)は意味不明だったので、おいおい出来たら良いな~という気持ちです

Ch3: Optimizing Fonts and Images

Image

Ch4: Layouts and Pages

Nested Routing

  • フォルダ配下の page.tsx は特別で、例えば app/page.tsx/, app/oyo/page.tsx/oyo にアクセスすると表示される

layout

  • layout.tsx は他のページと共有するUIを作れる
  • page コンポーネントは再レンダリングされるけど、レイアウトはされないので、(たぶん効率が良い)

Ch5: Navigating Between Pages

  • Linkを使うと、ページ丸ごとリロードすることなく遷移できる
    • 変わってないじゃん?と思ったが、ブラウザのページのアイコンがぐるぐるしているかどうかで判定できる
  • コードをprefetchしてくれるので、クリック時にはすでに目的のページが裏でロードされてる
    • めちゃはやく表示できる
    • 参考:https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#how-routing-and-navigation-works

usePathname()

  • クライアントコンポーネントにする必要がある
  • 参考:https://nextjs.org/docs/app/api-reference/functions/use-pathname

clsx

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,
    },
)}
  • pathname === link.hrefの時に、追加でbg-sky-100 text-blue-600が追加される

Ch.6: Setting Up Your Database

  • データを DB に登録することをseedingと呼ぶ

Ch.7: Fetching Data

Choosing how to fetch data

API

データベースクエリ

React Server Components

どうやら新しい機能らしい。デフォで使われてる

メリット

  • 非同期処理が楽になる?useEffect/useStateを使わなくても、async/awaitが使える?
  • サーバーで実行されるから
    • 結果のみクライアントに送られる(データ・ロジックはサーバーのみが保持できる)
    • 直接 DB をクエリ操作できる

問題点

  • request waterfallが発生する

    • 前のリクエストが終了したら、次のが実行される。並列処理されてない

      const revenue = await fetchRevenue();
      const latestInvoices = await fetchLatestInvoices(); // fetchRevenue()が終わるまで待つ
      const {
      numberOfInvoices,
      numberOfCustomers,
      totalPaidInvoices,
      totalPendingInvoices,
      } = await fetchCardData(); // fetchLatestInvoices()が終わるまで待つ
      
      • 対処法として、Promise.all()で並列に処理
        • どれかのリクエストがめっちゃ遅かったらどうなる…?
          • データを動的に読み込むのであれば、遅い処理があればそりゃページ読み込む速度遅くなるよね
      export async function fetchCardData() {
      try {
      const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
      const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
      const invoiceStatusPromise = sql`SELECT
              SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
              SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
              FROM invoices`;
      // Promise.all()で並列に処理
      + const data = await Promise.all([
          invoiceCountPromise,
          customerCountPromise,
          invoiceStatusPromise,
      ]);
      // ...
      }
      }
      
  • static rendering(静的レンダリング)なので、データが変化しても表示は変化しない

    • SQL が問い合わせてる先のデータが削除・追加されても、そのつど SQL が実行されるわけじゃない

fetching data for the dashboard overview page

  • export default async function Page()asyncawaitを使えるようにする
  • const data = await sql<Revenue>`SELECT * FROM revenue`;で SQL が実行できそう

Ch.8: Static and Dynamic Rendering

  • 静的レンダリング:ユーザーが訪れるたびに、キャッシュされたのが提供される
  • メリット
    • 早い
    • サーバーの負担が少ない
    • SEO対策になる。クローラーが喜ぶ
  • 向いてる場所
    • データを使わないサイト
    • みんな共通のデータ(不変)を使う
  • 動的レンダリング
    • クッキーとか、URL のパラメータとかの、リクエスト時にのみ得られる情報にアクセスできる
    • ただ、データのフェッチに時間がかかると、ページ読み込みもその分遅くなる

Ch.9: Streaming

Stream について学ぼう

  • データを小さいまとまり(チャンク)にして、送れるぜ!になったら各々送る
    • 早くなるよ
  • 方法
  1. loading.tsx ファイル
  • loading.tsxは next.js のSuspenseの特別なファイルらしい
    • page.tsxみたいな、特殊な奴
  1. <Suspense>コンポーネント
  • 何らかの条件が満たされるまで(例:データが読み込まれるまで)一部のレンダリングを延期できる
  • 動的コンポーネントを Susppense でラップ、動的コンポーネントのロード中に表示するフォールバックコンポーネントを渡す

Ch.10: Partial Prerendering

  • 元々、ルート(ページ)全体を静的・動的レンダリングのどちらかに統一する必要があった

    • if you call a dynamic function in a route (like querying your database), the entire route becomes dynamic.
    • でも、動的・静的どっちも使いたいね
      • 大部分は静的に生成、一部は動的にデータ取得とか!
  • Partial Prerendering(PPR): Next.js 14 で登場

    • Suspenseを使って、動的に読み込む場所のスペースを残して静的レンダリング->動的コンテンツを非同期に読み込みレンダリング
  • 使い方

    • Suspenseで動的なのをラップしてれば、基本的にコードを変えなくてもよい
// next.config.mjs
// 追記
const nextConfig = {
  experimental: {
    ppr: "incremental",
  }
};
// layout.tsx
export const experimental_ppr = true; // 追記
  • データフェッチを最適化しよう
    1. データベースのリージョンは近所に
    2. React Server Componentsを使うと、ロジック・データがサーバー内のみに存在する
      • クライアントの JS が頑張らなくていい
      • データがクライアントに漏れる心配ない
    3. 必要な分のデータは SQL で取得しよう
      • 毎回の通信するデータが減る
      • in-memory に変換するようの JS が減る
    4. データは並列に取得しよう
    5. Streaming で、重いデータが足を引っ張ってページがいつまでも表示されない…をなくそう
      • すべてロードされなくても、ユーザーが画面操作できるようにしたいね
    6. データフェッチを必要なコンポーネントに移すことで、どの部分をダイナミックにするかを分離できる

Ch.11: Adding Search and Pagination

  • Pagination?: 検索件数がたくさんあるときに、下に出てくるあれ

なんでURL search paramを使う?

  • 他の方法として、クライアントの方でstate管理もできる
  • メリット
    1. ブックマークや、他の人にURLでシェアできる
    2. サーバーサイドレンダリングと初期読み込み:URLパラメータはサーバー上で直接処理できるから、初期の状態をレンダリングするとき便利
    3. 分析・トラッキング:クライアントでごちゃごちゃしなくても、クエリ・フィルターがURLに直接埋め込まれてるので、容易にユーザーの挙動を追跡できる

検索機能で使う機能

  • useSearchParams: URLのパラメータを取得可能
    • /dashboard/invoices?page=1&query=pending -> {page: '1', query: 'pending'}.
  • usePathName: 現在のURLを取得可能
  • useRouter: ページの遷移が出来る(?)

debouncing(ディバウンシング)

  • 関数の発火を制限する
    • 現状:ユーザーが一文字入力するたびに、検索している->サーバーが大変
    • 理想:ユーザーが入力を止めた時に、発火させたい
  • 原理
    1. 発火:発火したら、タイマーがスタート
    2. 待機:もし再度発火 & タイマーが時間切れじゃない -> タイマーリセット
    3. 実行:もしタイマーが切れたら、関数実行 pnpm i use-debounce

Tip: defaultValue vs value

  • value: stateで入力値を管理してる
    • controlled components(?)にするため
    • reactがinputの状態を管理できる
  • defaultValue: stateで管理してない
    • inputのみが情報を管理・保持してる
<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()}
/>

useSearchParams hook vs searchParams props

  • useSearchParams: クライアントコンポーネント
  • searchParams: サーバーコンポーネント

Ch.12: Mutating Data (mutate: 変化する)

Server Actionとは

  • サーバー上でコードを非同期で実行できる
    • APIを作らなくてもよい!
    • クライアント/サーバーコンポーネントから実行可能
  • どうやら、セキュリティ面もいい感じらしい
    • encrypted closures, strict input checks, error message hashing, and host restrictions…

invoiceを作ろう

  • use server;: エクスポートされたのが、server actionになる。
    • クライアント・サーバーコンポーネントにて使用可能
  • formDataの情報を取得するためには、.get(name)を使う

    'use server';
    
    export async function createInvoice(formData: FormData) {
      const rawFormData = {
        customerId: formData.get('customerId'),
        amount: formData.get('amount'),
        status: formData.get('status'),
      };
    }
    
  • バリデーションしたい
    • typeof rawFormData.amount: 型が分かる
    • Zod: バリデーションライブラリ
    import { z } from 'zod';
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    };
    
    • floatをなるべく扱わないようにしよう(intで管理できるのであれば、そうする)
  • client-side router cashe
    • これ、良く分からない

invoiceをアップデートしよう

  • Dynamic Route Seements
    • 正確な「segment name」を知らない & ルートをデータを基に作りたい時に良い (ブログタイトル、製品ページ…)
  • server actionにはpropsを渡せないから、bindを使う必要がある
    • クライアントコンポーネントから実行されなくて、サーバーで実行されるため

Tip: UUID vs auto-incrementing key

  • auto-incrementing key: 短い
  • UUID: 長いけどたくさんのメリット
    • IDが衝突しない・ユニーク・列挙型攻撃に強い -> 大きいDBに向いてる

Ch.13: Handling Errors

error.js notFound not-found.js

error.tsx

  • エラーが発生したら表示するページ?
    • どんなエラーでも!
  • fallback(前の画面に戻る?)UIを表示する
  • クライアントコンポーネント(use client
  • 2つのpropsを受け取る
    • error: JSのError オブジェクト
    • reset:route segment(前の画面?)を再レンダリングする
  • ファイルを作って、中身を書く

notFound関数

  • リソースが存在しない時のみ
  • 下のように、エラーが出そうなところでハンドリング、同じ階層にnot-found.tsxを作成
    import { notFound } from 'next/navigation';
    if (!invoice) {
          notFound();
    }
    

Ch.14: Improving Accessibility

Form Validation

  • useActionState(クライアントコンポーネントで使う)
const FormSchema = z.object({
  id: z.string(),
  customerId: z.string({
    invalid_type_error: 'Please select a customer.',
  }),
  amount: z.coerce
    .number()
    .gt(0, { message: 'Please enter an amount greater than $0.' }),
  status: z.enum(['pending', 'paid'], {
    invalid_type_error: 'Please select an invoice status.',
  }),
  date: z.string(),
});
  • 色々な機能が、色々なファイルに散らばってて、何が何だか

Ch.15: Adding Authentication

Authetication vs Authorization

  • Authentication(認証):身分を証明する行為(ユーザー名とパスワード的な)
  • Authorization(認可): 認証された時、アプリをどこまで使っていいか決める(?)

NextAuth.js

  • 認証の機能を使える
    • セッションの管理
    • サインイン・サインアウト…
pnpm i next-auth@beta

下の値を.envのAUTH_SECRETに記入する

openssl rand -base64 32
  • やることがたくさんあって、何が何だか

memo

よくわからんエラー

Next.js (15.0.0-canary.56) is outdated (learn more)

Unhandled Runtime Error
Error: Unsupported Server Component type: undefined
  • これはpnpm add next@canaryしたら直った。
  • npm i next@latest, npm i next@canaryは無力だった

import の謎

import { lusitana } from '@/app/ui/fonts';import CardWrapper from '@/app/ui/dashboard/cards';は何が違う? なんで二通りの書き方が存在する?

  • 前者:名前付きインポート
    • 下みたいに、モジュールが複数のエクスポート持つときに使う
    export const lusitana = 'some-value';
    export const anotherFont = 'another-value';
    
  • 後者:デフォルトインポート
    • モジュールが一つしかエクスポートしない時に使う
    const CardWrapper = () => { /* ... */ };
    export default CardWrapper;
    

use client?

  • イベントリスナーとフックを使えるらしい