Open19

Next.jsを学ぶ

kmkkiiikmkkiii

Chapter1 Getting Started

  • 学習用プロジェクトのチェックアウト
  • ディレクトリ構成
    • /app:アプリケーションのすべてのルート、コンポーネント、ロジックを含みます。
      • /app/lib:再利用可能なユーティリティ関数やデータフェッチ関数など、アプリケーションで使われる関数が含まれます。
      • /app/ui:カード、テーブル、フォームなど、アプリケーションのすべてのUIコンポーネントが含まれます。時間を節約するために、これらのコンポーネントはあらかじめスタイル設定されています。
    • /public:画像など、アプリケーションのすべての静的アセットが含まれます。
    • /scripts/:後の章でデータベースにデータを投入するために使うファイルが格納されています。
  • このプロジェクトではデータ型を手動で定義しているが、DBスキーマから肩を自動生成するPrismaを使うことを推奨している
kmkkiiikmkkiii

Chapter2 CSS Styling

  • TailwindとかCSS Modules
  • 以下のimport動かなかったので修正
/app/page.tsx
- import styles from './home.module.css';
+ import styles from '@/app/ui/home.module.css';
//...
<div className={styles.shape}></div>;
  • clsx
    • 状態やその他の条件に基づいて、要素に条件付きでスタイル/クラス名を切り替えられるようにするライブラリ
kmkkiiikmkkiii

Chapter3 Optimizing Fonts and Images

  • 累積レイアウトシフト(Optimizing Fonts and Images)
    • Googleがウェブサイトのパフォーマンスとユーザー体験を評価するために使用する指標
    • ブラウザが最初にフォールバックフォントまたはシステムフォントでテキストをレンダリングし、それが読み込まれるとカスタムフォントに置き換わるときに発生
    • next/fontを使うとビルド時にフォントをダウンロードして、静的アセットと一緒にホスティングしてくれるのでアクセス時にフォントのネットワークリクエストが発生しない
  • フォントを滑らかにする Tailwind antialased クラス
  • Image(next/image)
    • <Image>コンポーネントは、HTMLの<img>タグを拡張したもので、以下のような自動画像最適化機能を備えている:
      • 画像の読み込み時に自動的にレイアウトがずれるのを防ぎます。
      • 画像をリサイズして、ビューポートの小さいデバイスに大きな画像を送らないようにする。
      • デフォルトでの画像の遅延読み込み(画像はビューポートに入ると読み込まれます)。
      • ブラウザがサポートしている場合、WebPやAVIFのような最新のフォーマットで画像を提供する。
kmkkiiikmkkiii

Chapter5 Navigating Between Pages

  • Linkコンポーネント(next/link)
    • ページ全体を更新せず、ページ間を遷移する
  • 現在どのページにいるのかを示すアクティブなリンク
    • usePathName()
  • 自動コード分割により、特定のページでエラーをスローしてもアプリケーションの他の部分は動作する
  • 本番環境では、Linkコンポーネントがビューポートに入るとリンクされたコードがプリフェッチされる
kmkkiiikmkkiii

Chapter6 Setting Up Your Database

  • Vercel Postgres のセットアップ
kmkkiiikmkkiii

Chapter7 Fetching Data

  • 非同期RSCによるデータフェッチ
    • Next.jsはデフォルトはReact Server Componentsを使用し、必要に応じてClient Componentsを使用できる
    • Server Componentsはpromiseをサポートしており、useEffectやuseStateを使うことなく、async/awaitを使用できる
    • サーバ上で実行されるため、APIレイヤーを追加しなくてもDBに直接問い合わせできる
  • Vercel Postgres SDKを使用するとSQLインジェクションから保護される
  • リクエストウォーターフォールとパラレルデータフェッチ
    • Promise.all(), Promise.allSettled()
kmkkiiikmkkiii

Chapter 8 Static and Dynamic Rendering

  • Next.js 14の実験的な機能が紹介されている
  • Dynamic Rendering
    • Server Componentやデータ取得関数の内部でunstable_noStoreというNext.js APIを使用すると、静的レンダリングを省略できる
    • unstable_noStoreはexperimental APIなので、安定的なAPIを使用したい場合はSegment Config Option export const dynamic = "force-dynamic"を使う
    • アプリケーションは最も遅いデータ・フェッチと同じ速度しか出せない
kmkkiiikmkkiii

Chapter 9 Streaming

  • ストリーミングとは、ルートを小さな "チャンク "に分割し、準備ができ次第、サーバーからクライアントに順次ストリーミングするデータ転送技術
  • Streamingを実装する方法
    • ページレベルで、loading.tsxファイルを使用する
    • 特定のコンポーネントに<Suspense>を使用する
  • loading.tsx(ページ全体のStreaming)
    • Suspense上に構築された特別なファイル
    • ページがロードされる間に代替として表示するUIを定義できる
    • UXを向上させるために「Loading...」というテキストの代わりにスケルトンを表示すると良い
    • Route groups
      • Route groupを使用すると、URLパス構造に影響を与えることなく、ファイルを論理的なグループにまとめることができる
      • 括弧 () を使用して新しいフォルダを作成すると、その名前は URL パスには含まれない
        • つまり、/dashboard/(overview)/page.tsxは/dashboardになる
  • Susppense
    • Suspenseを使うと、何らかの条件が満たされるまで(例えばデータが読み込まれるまで)、アプリケーションの一部のレンダリングを延期できる
    • 複数のコンポーネントを同時にロードしたいときは、ラッパーコンポーネントでグループ化した上でSuspenseで囲む
  • Suspenseの境界線をどこに置くか
    • アプリケーションによって異なり、正解はないが、いくつかのポイントがある
      • ユーザーにどのようにページを体験してもらいたいか
      • どのコンテンツを優先するか
      • コンポーネントがデータ取得に依存している場合
    • 一般的には、データの取得を必要なコンポーネントに移し、そのコンポーネントをSuspenseでラップするのがよい方法だが、必要に応じて、セクションやページ全体をストリーミングするのもあり
      • これにより、特定のコンポーネントをストリーミングし、UIがブロックされるのを防ぐことができる
kmkkiiikmkkiii

Chapter10 Partial Prerendering (Optional)

  • Next.js v14で導入された実験的機能
  • Partial Prerendering(PPR)
    • 静的なローディングshellでルートをレンダリングする一方で、動的な部分を残すことができる。つまり、ルートの動的な部分を分離することができる
    • Hole
      • リクエスト時に動的コンテンツが非同期でロードされる場所
    • Partial PrerenderingはReactのConcurrent APIを活用し、Susppenseを使用して、何らかの条件が満たされるまで(例えばデータがロードされるまで)アプリケーションの一部のレンダリングを延期する
    • フォールバックは、他の静的コンテンツと一緒に最初の静的ファイルに埋め込まれる。ビルド時(もしくは再検証時)に、ルートの静的部分はプリレンダリングされ、残りはユーザーがルートをリクエストするまで延期される
    • Susppenseでコンポーネントをラップしても、コンポーネント自体が動的になるわけではなく (この動作を実現するために unstable_noStore を使用した)、Susppenseはルートの静的な部分と動的な部分の境界として使用される
    • 部分的なプリレンダリングの素晴らしいところは、それを使うためにコードを変更する必要がないこと。ルートの動的な部分をラップするためにSusppenseを使用している限り、Next.jsはルートのどの部分が静的で、どの部分が動的であるかを知ることができる

ここまでの要約

アプリケーションのデータ・フェッチを最適化するために行なったこと:

  • アプリケーション・コードと同じリージョンにデータベースを作成し、サーバーとデータベース間の待ち時間を短縮
  • React Server Componentsでサーバー上のデータをフェッチ。これにより、高価なデータフェッチとロジックをサーバー上に保持し、クライアント側のJavaScriptバンドルを減らし、データベースの秘密がクライアントに公開されるのを防ぐ
  • SQLを使用して必要なデータのみをフェッチし、リクエストごとに転送されるデータ量と、インメモリでデータを変換するために必要なJavaScriptの量を削減
  • JavaScriptでデータ・フェッチを並列化
  • 遅いデータ・リクエストがページ全体をブロックするのを防ぎ、ユーザーがすべての読み込みを待つことなくUIとのやりとりを開始できるように、ストリーミングを実装
  • データの取得を必要なコンポーネントに移動させ、Partial Prerenderingに備えてルートのどの部分をダイナミックにすべきかを分離
kmkkiiikmkkiii

Chapter11 Adding Search and Pagination

  • useSearchParams
    • 現在のURLのパラメータにアクセスできる
      • /dashboard/invoices?page=1&query=pendingの場合、{page:'1', query:pending'}を返す。
  • usePathname
    • 現在のURLのパス名を読み取る。
      • /dashboard/invoices の場合、usePathname は '/dashboard/invoices' を返す。
  • useRouter
    • usePathnameとuseRouter()のreplaceを使って、inputのonChangeをトリガーにしてURLをリアルタイムに更新できる。
  • defaultValue vs. value / Controlled vs. Uncontrolled
    • ReactでState管理するならvalue、State使わないならdefaultValueでinputがState管理する(URLの検索パラメータに保存するのもOK)
  • useSearchParams()フックとsearchParamsプロップの使い分け
    • どちらを使うかは、クライアントで作業しているかサーバで作業しているかによる
    • クライアント→useSearchParams()
    • サーバ→searchParms()
  • Debouncing
    • 関数が起動するレートを制限するプログラミング手法
      • ユーザが入力を止めたときだけデータベースに問い合わせを行いたい
    • use-debounceのuseDebouncedCallback()を使うと関数をラップして、指定時間アクションがあるまでコード実行を待機させることができる
kmkkiiikmkkiii

Server ComponentとClient Component

  • 従来のSSRはハイドレーションするため、ブラウザ・サーバー両方で実行されるものだった
    • ハイドレーション = SSRやSSGにより生成されたページにイベントハンドラーをアタッチして、インタラクティブなUIにすること
  • Server Componentは完全にサーバーサイドで実行される
    • ブラウザで実行すべきJavaScriptが送信されず、ハイドレーションもない
    • ブラウザ・サーバー両方で実行されるコンポーネントとしてClient Componentを実装する
    • use clientディレクティブを宣言するとブラウザ向けにバンドルされて送信される
      • ブラウザに送信するJavaScriptを最小限に抑えられ、パフォーマンス向上が見込める
  • 親コンポーネントでuse clientディレクティブを宣言している場合、子コンポーネントで再宣言する必要はない
    • use clientを宣言したファイルではなく、宣言されたコンポーネントファイルをRootとしたSubtree全体がClient Componentになる()
kmkkiiikmkkiii

拡張されたfetch関数

  • Next.jsでは静的データをキャッシュするためにfetch関数が拡張されている
    • デフォルトで静的データ取得として扱われ、キャッシュされる
    • 動的データ取得の場合、{ cache: "no-store" }を指定する
  • npm run devで起動した場合キャッシュの挙動を正確に把握できない
    • 以下が必要
      • .next/cache/fetch-cacheを削除
      • buildしてnpm startで起動
kmkkiiikmkkiii

レンダリング

  • build時に全てのRouteに対してレンダリングを試行し、静的か動的かを判定する
    • 動的データ取得
    • cookies, headers, searchParamsといった動的関数
    • Dynamic Segmentの含まれるRouteの場合でprops.paramsを参照している
kmkkiiikmkkiii

SuspenseとError Boundary

  • Suspense
    • 子要素が読み込みを完了するまでフォールバックUIを表示する機能
  • Error Boundary
    • 子コンポーネントでErrorがスローされた場合にフォールバックUIを表示する実装
    • layout.tsxのエラーは親Route Segmentのerror.tsxで処理される
     <Layout>
       <Template>
         <ErrorBoundary fallback={<Error />}>
           <Suspense fallback={<Loading />}>
             <ErrorBoundary fallback={<NotFound />}>
               <Page />
             </ErrorBoundary>
           </Suspense>
         </ErrorBoundary>
       </Template>
    </Layout>
    
kmkkiiikmkkiii

API RoutesはPages Routerの機能で似ているが、App Routerで実装するWeb APIはRoute Handlerと呼ぶ。

kmkkiiikmkkiii

継承されたメタデータの参照はgenerateMetadataの第2引数のResolvingMetadataから参照できる。

export async function generateMetadata(
    { params }: Props,
    parent: ResolvingMetadata
): Promise<Metadata> {
     const fuga = await getFuga(params.fugaId);
     const { hoge } = await parent;
     return { title: `${fuga.title} | ${hoge?.absolute}` };
 }