Open15

Lean Next.js

ゆーとゆーと

https://nextjs.org/learn?utm_source=next-site&utm_medium=homepage-cta&utm_campaign=home

Chapter 1 / Getting Started

Creating a new project

terminal
npx create-next-app@latest nextjs-dashboard --use-npm --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example"

Exploring the project

ゼロからコードを書くチュートリアルとは異なり、このコースのコードの多くはすでに書かれています。これは、既存のコードベースで作業することになる、実際の開発現場をよりよく反映しています。

私たちの目標は、すべてのコードを書かなくても、Next.jsの主な機能の学習に集中できるようにすることです。


Folder structure

プロジェクトは以下のようなフォルダ構造になっていることがわかるだろう:

  • /app:アプリケーションのすべてのルート、コンポーネント、ロジックを含みます。
  • /app/lib:再利用可能なユーティリティ関数やデータフェッチ関数など、アプリケーションで使われる関数が含まれます。
  • /app/ui:カード、テーブル、フォームなど、アプリケーションのすべてのUIコンポーネントが含まれています。
    時間を節約するために、これらのコンポーネントはあらかじめスタイル設定されています。
  • /public:画像など、アプリケーションのすべての静的アセットが含まれます。
  • /scripts/:後の章でデータベースにデータを投入するために使うファイルが格納されています。
  • Config Files : next.config.jsのような設定ファイルもアプリケーションのルートにあります。
    これらのファイルのほとんどは、create-next-appを使用して新しいプロジェクトを開始するときに作成され、あらかじめ設定されています。
    このコースでは、これらのファイルを変更する必要はありません。

これらのファイルを自由に探索し、コードが行っていることのすべてをまだ理解していなくても心配しないでください。


Placeholder data

ユーザー・インタフェースを構築するとき、いくつかのプレースホルダ・データがあると便利です。データベースやAPIがまだ利用可能でない場合:

  • JSON形式またはJavaScriptオブジェクトとしてプレースホルダデータを使用する。
  • mockAPIのようなサードパーティのサービスを使う。

このプロジェクトでは、app/lib/placeholder-data.jsにいくつかのプレースホルダー・データを用意した。
ファイル内の各JavaScriptオブジェクトは、データベースのテーブルを表します。


TypeScript

また、ほとんどのファイルの接尾辞が.tsまたは.tsxであることに気づくかもしれない。
これはサンプルがTypeScriptで書かれているからです。
私たちは、堅牢なアプリケーションを構築するために必要なスキルを身につけるだけでなく、現代のWebの状況を反映したコースを作りたかったのです。

とりあえず、/app/lib/definitions.tsファイルを見てほしい。
ここでは、データベースから返される型を手動で定義しています。
TypeScriptを使うことで、コンポーネントやデータベースに間違ったデータ形式を誤って渡さないようにすることができる。
例えば、amountにnumberではなくstringを渡すような場合だ。

もしあなたがTypeScript開発者なら:

  • Next.jsは、プロジェクトがTypeScriptを使用しているかどうかを検出し、必要なパッケージと設定を自動的にインストールします。
    また、Next.jsにはコードエディタ用のTypeScriptプラグインが付属しており、自動補完と型安全性をサポートします。
  • 私たちはデータ型を手動で宣言していますが、型の安全性を高めるには、データベーススキーマに基づいて型を自動生成するPrismaのようなツールをお勧めします。


Running the development server

npm iを実行して、プロジェクトのパッケージをインストールする。
npm run devで開発サーバーを起動する。

ゆーとゆーと

Chapter 2 / CSS Styling

現在、ホームページにはスタイルがありません。Next.jsアプリケーションをスタイル設定するさまざまな方法を見てみましょう。

この章で取り上げるトピックは以下の通りです。

  • アプリケーションにグローバルCSSファイルを追加する方法。
  • スタイリングの2つの異なる方法:Tailwind と CSS モジュール。
  • clsxユーティリティパッケージを使って条件付きでクラス名を追加する方法。


Global styles

app/ui/global.cssというファイルを使って、アプリケーションの全てのルートにCSSのルールを追加できます。
CSSのリセットルール、リンクのようなHTML要素のサイト全体のスタイルなどです。

アプリケーションのどのコンポーネントでもglobal.cssをインポートできますが、通常はトップレベルのコンポーネントに追加するのがよい習慣です。
Next.jsでは、これがroot layout[1]です(詳細は後述します)。

グローバルスタイルをアプリケーションに追加するには、/app/layout.tsxに移動して、global.cssファイルをインポートします。するとスタイルが変更されることが確認できます。

CSSルールを追加していないのに、スタイルはどこから来たのですか?
global.cssの中を見てみると、@tailwindディレクティブがいくつかあることに気づくだろう。

clobal.css
@tailwind base;
@tailwind components;
@tailwind utilities;

input[type='number'] {
  -moz-appearance: textfield;
  appearance: textfield;
}

input[type='number']::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type='number']::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}


Tailwind

Tailwindは、JSXマークアップに直接ユーティリティクラスを素早く記述できるようにすることで、開発プロセスをスピードアップするCSSフレームワークです。
CSSはコンポーネントにスコープされるので、スタイルの衝突や個別のスタイルシートの維持を心配する必要はありません。

Tailwindでは、クラス名[2]を追加することでスタイル要素を追加します。たとえば、text-blue-500 というクラスを追加すると、<h1> テキストが青くなります:

create-next-appを使用して新しいプロジェクトを開始すると、Next.jsはTailwindを使用するかどうかを尋ねます。yesを選択すると、必要なパッケージが自動的にインストールされ、アプリケーションにTailwindが設定されます。

/app/page.tsxを見ると、サンプルでTailwindクラスを使っていることがわかります。

page.tsx
import AcmeLogo from '@/app/ui/acme-logo';
import Link from 'next/link';
 
export default function Page() {
  return (
    // These are Tailwind classes:
    <main className="flex min-h-screen flex-col p-6">
      <div className="flex h-20 shrink-0 items-end rounded-lg bg-blue-600 p-4 md:h-52">
    // ...
  )
}

脚注
  1. root layout: トップレベルのコンポーネントとして、appディレクトリにroot layout(layout.tsxなど)を配置する必要あり。Next.jsは<html>タグと<body>タグを自動的に作成しないため、ルートレイアウトで定義する必要があります。ここでの設定が全てのレイアウトに適用される。 ↩︎

  2. className: CSSを要素に適用させるための識別子。要素名。 ↩︎

ゆーとゆーと

Chapter 3 / Optimizing Fonts and Images ~①~

前の章では、Next.jsアプリケーションのスタイル設定方法を学びました。
引き続き、カスタムフォントとヒーロー画像を追加して、ホームページを作成してみましょう。

この章で取り上げるトピックは以下の通りです。

  • next/fontでカスタムフォントを追加する方法。
  • next/imageで画像を追加する方法。
  • Next.jsでフォントと画像を最適化する方法。


Why optimize fonts?

フォントはウェブサイトのデザインにおいて重要な役割を果たしますが、プロジェクトでカスタムフォントを使用すると、フォントファイルの取得と読み込みが必要になり、パフォーマンスに影響を与える可能性があります。

Cumulative Layout Shiftは、Googleがウェブサイトのパフォーマンスとユーザー体験を評価するために使用する指標です。
フォントに関しては、ブラウザが最初にテキストを代替フォントやシステムフォントで表示し、カスタムフォントがロードされるとそれに切り替えることで、レイアウトのシフトが発生します。
この切り替えにより、テキストのサイズ、間隔、またはレイアウトが変わり、それに伴い周囲の要素が移動することがあります。

Next.jsはnext/fontモジュールを使用することで、ビルド時にフォントファイルをダウンロードし、他の静的アセットと共にホスティングすることで、フォントの最適化を自動的に行います。
これにより、アプリケーションを訪れたユーザーに対して、パフォーマンスに影響を与える追加のネットワークリクエストが発生しなくなります​。
(フォントファイルを他の静的アセットと一緒にホスティングするため、追加のネットワークリクエストが発生しません。)

アプリケーションにカスタムGoogleフォントを追加して、この仕組みを確認してみましょう!


Adding a primary font

app/uiフォルダに、fonts.tsという新しいファイルを作成します。このファイルは、アプリケーション全体で使用されるフォントを保持するために使用します。

next/font/googleモジュールからInterフォントをインポートしてください。
次に、ロードしたいサブセット[1]を指定する。この場合はlatin

fonts.ts
import { Inter } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
// Interというフォント内で既に定義されているlatinというサブセットを使用。

最後に、/app/layout.tsxの<body>要素にフォントを追加します:

layyout.tsx
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

<body>要素にInterを追加することで、アプリケーション全体にフォントが適用されます。ここでは、フォントを滑らかにする Tailwind antialiased クラスも追加しています。
このクラスを使用する必要はありませんが、フォントに良いタッチを加えます。

ブラウザに移動し、dev toolsを開いてbody要素を選択します。
スタイルの下にInterInter_Fallbackが適用されているのが見えるはずです。
アプリケーションの特定の要素にフォントを追加することもできます。


脚注
  1. subset: フォントのsubsettingは、フォントファイルから不要な文字を削除して、ファイルサイズを小さくする技術です。たとえば、あるフォントには数千もの異なる文字や記号が含まれているかもしれませんが、ウェブサイトで実際に使用するのはそのうちのごく一部だけかもしれません。サブセッティングを利用することで、ウェブサイトが必要とする特定の文字だけを含むようにフォントをカスタマイズでき、これによりフォントファイルのサイズを削減し、ウェブサイトのロード時間を短縮できます。 ↩︎

ゆーとゆーと

Chapter 3 / Optimizing Fonts and Images ~②~

Why optimize images?

Next.jsは、トップレベルの/publicフォルダに画像などの静的アセットを提供できます。
/publicフォルダ内のファイルは、アプリケーション内で参照できます。

      <img
        src="/hero.png"
        alt="Screenshots of the dashboard project showing desktop and mobile versions"
      />

2つの画像があって、ユーザーのデバイスがデスクトップかモバイルかによって表示する画像を切り替えたい場合、以下を手動で行わなければならない。

  • 異なる画面サイズに対応できるようにする。
  • デバイスごとに画像サイズを指定する。
  • 画像の読み込みに伴うレイアウトのずれを防ぐ。
  • ユーザーのビューポート外にある画像を遅延ロードする。

これらの最適化を手動で処理する代わりに、next/imageコンポーネントを使用して画像を自動的に最適化することができます。


The <Image> component

<Image>コンポーネントは、HTMLの<img>タグを拡張したもので、以下のような自動画像最適化機能を備えている。

  • 画像の読み込み時に自動的にレイアウトがずれるのを防ぎます。
  • 画像をリサイズして、ビューポートの小さいデバイスに大きな画像を送らないようにする。
  • デフォルトでの画像の遅延読み込み(画像はビューポートに入ると読み込まれます)。
  • ブラウザがサポートしている場合、WebPやAVIFのような最新のフォーマットで画像を提供する。


Adding the desktop hero image

<img>タグを<Image>コンポーネントに置き換えてみよう。
/app/page.tsxファイルに、next/imageからコンポーネントをインポートします。

 import Image from 'next/image';
 // ....
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop and mobile versions"
      />

ここでは、幅を1000ピクセル、高さを760ピクセルに設定しています。
レイアウトのずれを避けるために、画像の幅と高さを設定するのは良い習慣です。

また、モバイル画面では画像をDOMから削除するためにhiddenクラスが、デスクトップ画面では画像を表示するためにmd:blockが指定されている。

  • hidden: すべての画面サイズにおいて、要素を非表示にします(display: none;)。
  • md:block: ミディアムサイズの画面(デフォルトでは768ピクセル以上の幅)で、要素をブロック要素として表示します(display: block;)。

Tailwind CSSのレスポンシブプレフィックス(ここではmd:)を使用することで、異なる画面サイズにおけるスタイルの変更が可能になります。この場合、小さい画面では要素が非表示となり、ミディアムサイズ以上の画面で要素が表示されるようになります。
要するに、このクラスを適用した要素は、モバイルデバイスや小さい画面では隠され、タブレットサイズ以上のデバイスでのみ表示されることになります。これにより、レスポンシブなデザインが実現されます。

ゆーとゆーと

Chapter 4 / Creating Layouts and Pages

あなたのアプリケーションにはまだホームページしかありません。
layoutspagesを使ってより多くのルートを作る方法を学びましょう。

ここで取り上げるトピックは以下。

  • ファイルシステムのルーティングを使用して、/logindashboardページを作成する。
  • 新しいルートセグメントを作成する際のフォルダとファイルの役割を理解する
  • 複数のダッシュボードページで共有できるレイアウトを作成する。
  • コロケーション、部分レンダリング、ルートレイアウトの意味を理解する


Nested routing

Next.jsでは、ネストされたルートを作成するためにfoldersが使用されるファイルシステムルーティングが使用されます。各フォルダはURLセグメントにマッピングされたルートセグメントを表します。

≒ Next.jsでは、基本的にはファイルとフォルダでパスが作成される。
URL例: /login/home

/app
├─ page.tsx (ホームページ)
└─ login
    ├─ page.tsx (ログインページ)
    └─ home
         └─ page.tsx (ホームページ)


Creating the dashboard page

appの中にdashboardという新しいフォルダを作成してください。
次に、dashboardフォルダの中に、新しいpage.tsxファイルを作成します:


Creating the dashboard layout

ダッシュボードにも、複数のページで共有される何らかのナビゲーションがあります。
Next.jsでは、特別なlayout.tsxファイルを使用して、複数のページで共有されるUIを作成できます。ダッシュボード用のレイアウトを作成してみましょう!

dashboardフォルダ内にlayout.tsxという新しいファイルを追加し、次のコードを貼り付けます:

layout.tsx
import SideNav from '@/app/ui/dashboard/sidenav';
 
// ページ共有のレイアウト設定。このフォルダ階層以下(children)が共有対象。
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
      <div className="w-full flex-none md:w-64">
        <SideNav />  //←これが共有して表示される。
      </div>
      <div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
    </div>
  );
}

まず、<SideNav />コンポーネントをレイアウトにインポートしています。このファイルにインポートしたコンポーネントはすべてレイアウトの一部になります。

Layoutコンポーネントはchildren propを受け取ります。childrenはページまたは別のレイアウトのいずれかになります。あなたの場合、/dashboard内のページは自動的にLayout内にネストされます:

Next.jsでのLayoutは、ページの共通部分を作成する方法。
<SideNav /> のようなコンポーネントをLayoutに取り込むことで、それが全てのページに表示されるようになる。

Layout コンポーネントは children という特別なプロパティを受け取る。
このchildrenは、ページまたは別のレイアウトを意味します。
/dashboard 内のページは自動的にLayoutに組み込まれ、結果として共通のレイアウトを持つことになる。

レイアウトを使用する利点の1つは、ナビゲーション時にページコンポーネントだけが更新され、レイアウトは再レンダリングされないことです。Next.jsでは、これをpartial renderingと呼びます:


Root layout

第3章では、Interフォントを別のレイアウトにインポートしました:/app/layout.tsx
このレイアウトは必須で、root layoutと呼ばれます。
root layoutに追加したUIはアプリケーションの全てのページで共有されます
root layoutを使って<html>タグと<body>タグを修正し、メタデータを追加できます(メタデータについては後の章で詳しく学びます)。

今作成した新しいレイアウト(/app/dashboard/layout.tsx)はダッシュボードページに固有なので、上記のルートレイアウトにUIを追加する必要はありません。

ゆーとゆーと

Chapter 5 / Navigating Between Pages

前の章では、ダッシュボードのレイアウトとページを作成しました。
次はユーザーがダッシュボードのページ間を移動できるようにリンクを追加してみましょう。

ここで取り上げるトピックは以下。

  • next/linkコンポーネントの使い方。
  • usePathname()フックを使ってアクティブなリンクを表示する方法。
  • Next.jsでクライアント側ナビゲーションがどのように動作するか。


Why optimize navigation?

ページ間をリンクするには、伝統的に<a> HTML要素を使用します。
今のところ、サイドバーのリンクは<a>要素を使っていますが、ブラウザでhome、invoices、customersのページを移動した時に、ページ全体が更新されてしまっています。


The <Link> component

Next.jsでは、アプリケーションのページ間をリンクするためにLinkコンポーネントを使うことができます。
<Link>を使用すると、JavaScriptでclient-siden nabigation[1]を行うことができます。
アプリケーションの一部はサーバー上でレンダリングされますが、ナビゲーションは高速になり、ページ全体をリフレッシュすることもありません。

ここでは、<Link>コンポーネントの使い方を説明します。

/app/ui/dashboard/nav-links.tsxを開き、next/linkからLinkコンポーネントをインポートし、<a>タグを<Link>に置換します。

これで、完全な更新を見ることなくページ間を移動できるようになるはずだ。


ユーザーが現在どのページにいるのかを示すために、アクティブなリンクを表示することは一般的なUIパターンです。これを実現するために、URLからユーザーの現在のパスを取得する必要があります。
Next.jsは、usePathname()というhookを提供していて、これを使うと現在のパスを確認できる。

usePathname()はhookなので、nav-links.tsxをClient Componentにする必要があります。

  1. Reactの "use client "ディレクティブをファイルの先頭に追加[2]し、next/navigationからusePathname()をインポートします。
  2. 次に、コンポーネント内のpathnameという変数にパスを代入する。
  3. CSS stylingの章で紹介したclsxライブラリを使えば、リンクがアクティブなときに条件付きでクラス名を適用することができる。ここではlink.hrefpatynameと一致する場合、リンクは青いテキストと水色の背景で表示されます。
  4. これでアクティブなリンクが青くハイライトされているはずです。
コード例
tsx
+ 'use client';
 
import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
+ import { usePathname } from 'next/navigation';
+ import clsx from 'clsx';
 
// ...
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>
        );
      })}
    </>
  );
}


Automatic code-splitting and prefetching

クライアントサイドのナビゲーションに加えて、Next.jsは自動的にアプリケーションをルートセグメントごとにコード分割します。これは、ブラウザが最初のロード時にすべてのアプリケーションコードをロードする従来のSPAとは異なります。

ルートによってコードが分割されるということは、ページが分離されるということです。特定のページがエラーをスローしても、アプリケーションの残りの部分は動作します。

さらに本番環境では、<Link>コンポーネントがブラウザのビューポートに表示されるたびに、Next.jsはバックグラウンドでリンクされたルートのコードを自動的にプリフェッチします。ユーザーがリンクをクリックするころには、リンク先ページのコードはすでにバックグラウンドで読み込まれており、ページ遷移はほぼ瞬時に行われます!

Next.jsは、ページを移動するときにブラウザが必要な部分だけを読み込むようにしてくれます。これは、普通のシングルページアプリケーション(SPA)とは違い、最初にアプリのすべてのコードを読み込む必要がないということです。

各ページごとにコードを分けて読み込むということは、一つのページに問題があっても他のページには影響しないということです。

さらに、本番環境では、<Link> コンポーネントがブラウザの画面に表示されたら、Next.jsが裏側で自動的にそのリンク先のページのコードを読み込み始めます。ユーザーがリンクをクリックする頃には、そのページのコードは既に読み込まれているので、ページの移動がとても速いです!

脚注
  1. client-siden nabigation: tmp ↩︎

  2. use client: use clientというのは、ある部分のコードがブラウザでだけ動くようにするマークです。通常、ウェブページにはサーバーで動くコードとブラウザで動くコードの二種類があります。use clientをファイルの一番上に書くと、そのファイルのコードはすべてブラウザでしか動かないという印をつけることができます。簡単に言えば、use clientは「このコードはサーバーではなく、ユーザーのブラウザでだけ使ってね」と伝えるための特別な印です。 ↩︎

ゆーとゆーと

Chapter 6 / Setting Up Your Database

ダッシュボードの作業を続ける前に、いくつかのデータが必要です。この章では、@vercel/postgres を使って PostgreSQL データベースをセットアップします。
すでにPostgreSQLに慣れていて、自分のプロバイダを使いたい場合は、この章を飛ばして自分でセットアップしてもかまいません。


Create a GitHub repository & Create a Vercel account & Connect and deploy your project

アカウント作ってプロジェクトを登録。
// 割愛


Create a Postgres database

  1. postgresDBを作成。
  2. .envをコピペ。
  3. npm i @vercel/postgres を実行してVercel Postgres SDKをinstall。


Seed your database

データベースが作成されたので、初期データを入れてみましょう。

プロジェクトの/scriptsフォルダに、seed.jsというファイルがあります。
このスクリプトには、invoices、customers、user、revenueのテーブルを作成し、シード[1]するための指示が含まれています。

スクリプトはSQLを使用してテーブルを作成し、placeholder-data.jsファイルのデータを使用してテーブルを作成します。

次に、package.jsonファイルに、以下の行をスクリプトに追加する。

package.json
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "start": "next start",
+    "seed": "node -r dotenv/config ./scripts/seed.js"
  },

これがseed.jsを実行するコマンドだ。コマンドを実行する前に、まずnpm i bcrypt[2]を実行しなければならない。

次に、npm run seedを実行する。スクリプトの実行を知らせるconsole.logメッセージがターミナルに表示されるはずだ。


Exploring your database

vercleのDB内のデータが、/lib/placeholder-data.js定義のデータと一致することを確認。


Executing queries

vercleのコンソール上でQueryタブに切り替えて、データベースを操作することができます。
標準的なSQLコマンドをサポートしています。例えば、DROP TABLE customersと入力すると、"customers "テーブルがすべてのデータとともに削除されます!

脚注
  1. seed: 一般的にはDBを初期化するために初期データを投入することを指す。 ↩︎

  2. bcrypt: パスワードをハッシュ化するためのライブラリ。 ↩︎

ゆーとゆーと

Chapter 7 / Fetching Data

アプリケーションのデータを取得するさまざまな方法について説明し、ダッシュボードの概要ページに最も適切な方法を選択しましょう。

ここで取り上げるトピックは以下。

  • データをフェッチするためのいくつかのアプローチについて学ぶ:API、ORM、SQLなど。
  • Server Components を使用することで、バックエンドのリソースにより安全にアクセスできるようになります。
  • ネットワーク・ウォーターフォールとは何か。
  • JavaScriptパターンを使って並列データフェッチを実装する方法。


Choosing how to fetch data

API layer

APIはアプリケーションとデータベースの間の中間層です。APIを使う理由はいくつかあります。

  • 第三者サービスを利用している場合、そのサービスが提供するAPIを使います。
  • クライアントからデータを取得する場合、データベースの秘密情報をクライアントに晒さないように、サーバー上で動作するAPI層が必要です。

Next.jsでは、Route Handlersを使ってAPIエンドポイント[1]を作成できます。


Database queries

フルスタックのアプリケーションを作るときには、データベースとやりとりするロジックも書く必要がある。
PostgresのようなRDBの場合、SQLやPrismaのようなORM[2]でこれを行うことができます。

データベースのクエリを書かなければならないケースもいくつかあります。

  • APIエンドポイントを作成する際、データベースと対話するロジックを書く必要があります。
  • React Server Componentsを使ってサーバー上でデータを取得する場合、API層をスキップして直接データベースに問い合わせることができ、データベースの秘密情報をクライアントに晒すリスクなしに行えます。

Q: クライアントでデータを取得する際、データベースに直接問い合わせるべきではありません。

ReactとNext.jsでデータをフェッチする方法は、ほかにもいくつかあります。もっと詳しく知りたい場合は、Data Fetchingのドキュメントをご覧ください。

次のセクションでは、比較的新しいアプローチである非同期React Server Componentsを使用してデータをフェッチする方法を探ります。


Using Server Components to fetch data

Next.jsのアプリケーションでは、デフォルトで「React Server Components」が使われています。必要に応じて「Client Components」を選択することもできます。React Server Componentsでデータを取得することの利点は以下があります。

  • Server Componentsはサーバーで実行されるので、データの取得や複雑な処理をサーバー側で行い、その結果だけをクライアントに送信することができます。
  • Server Componentsはプロミスをサポートしているので、データ取得のような非同期タスクに対して簡単な解決策を提供します。useEffectuseState、データ取得のライブラリに頼ることなく、async/await構文を使用できます。
  • Server Componentsはサーバーで実行されるため、追加のAPIレイヤーを介さずに直接データベースにクエリを実行できます。

つまり、Server Componentsを使えば、データを効率的に取得して、その結果だけをユーザーに送ることができ、複雑なデータ取得の処理をサーバー側で完結させることができます。これにより、コードがシンプルになり、処理が高速化します。


Using SQL

サンプルのダッシュボードPJでは、VercelのPostgres SDKとSQLを使ってデータベースに問い合わせるプログラムを書きます。SQLを使う理由はいくつかあります:

  • SQLはRDBを問い合わせるための業界標準です(例えば、ORMは内部的にSQLを生成します)。
  • SQLの基本を理解することで、RDBの基礎を理解し、他のツールにもその知識を応用できます。
  • SQLは多様で、特定のデータを取得したり操作したりするのに適しています。
  • VercelのPostgres SDKはSQLインジェクションというセキュリティの脅威から保護してくれます。

もしSQLを使ったことがなくても心配はいりません、必要なクエリは提供されています。

/app/lib/data.tsに行くと、@vercel/postgresからsql関数をインポートしているのを見ることができます。この関数を使ってデータベースに問い合わせることができます。

どのServer Component内でもsqlを呼び出すことができます。しかし、より簡単にコンポーネントをナビゲートできるように、すべてのデータクエリをdata.tsファイルに保持し、コンポーネントにインポートできるようにしています。


Fetching data for <LatestInvoices/>

<LatestInvoices /> コンポーネントでは、最新の5つの請求書を日付順に取得する必要があります。

全ての請求書を取得してJavaScriptで並び替える方法もあります。データが少なければ問題ないですが、アプリケーションが大きくなると、リクエスト毎に転送するデータ量と並び替えに必要なJavaScriptの量が増加し、効率が悪くなります。

そうする代わりに、最新の請求書をメモリ内で並び替えるのではなく、SQLクエリを使って最後の5つの請求書のみを取得することができます。

例えば、これはdata.tsファイルのSQLクエリです。
ページで fetchLatestInvoices 関数をインポートします。

/app/lib/data.ts
// Fetch the last 5 invoices, sorted by date
const data = await sql<LatestInvoiceRaw>`
  SELECT invoices.amount, customers.name, customers.image_url, customers.email
  FROM invoices
  JOIN customers ON invoices.customer_id = customers.id
  ORDER BY invoices.date DESC
  LIMIT 5`;

注意しなければならないことが2つある。

  1. データリクエストが意図せず互いにブロックされ、リクエストウォーターフォールが発生しています。
  2. デフォルトでは、Next.jsはパフォーマンスを向上させるためにルートを**プリレンダリング(静的レンダリング)**します。そのため、データが変更されてもダッシュボードには反映されません。

この章ではその1について説明し、次の章ではその2について詳しく見ていきましょう。


What are request waterfalls?

リクエストのウォーターフォールとは、ネットワークリクエストが連続して行われ、一つのリクエストが完了するのを次のリクエストが待つことを指します。データを取得する場合、あるリクエストの結果が返ってきて初めて次のリクエストを開始することができます。

例えば、fetchRevenue() という関数で収益データを取得してからでないと、fetchLatestInvoices() という最新の請求書データを取得する関数を実行することができない、という流れになります。

このパターンが常に悪いわけではありません。条件を満たした上で次のリクエストを行いたい場合には、このようなウォーターフォールが望ましいこともあります。
例えば、ユーザーのIDとプロファイル情報を先に取得し、そのIDをもとにそのユーザーの友達リストを取得する場合などです。ここでは、それぞれのリクエストが前のリクエストから返されたデータに依存しています。

しかし、この挙動は意図しないものであり、パフォーマンスに影響を与える可能性があります。リクエストが互いに依存しているため、一つ一つが遅れると全体の処理時間が長くなってしまうのです。


Parallel data fetching

ウォーターフォールを避ける一般的な方法は、すべてのデータ要求を同時に、つまり並行して開始することである。

JavaScriptでは、Promise.all()またはPromise.allSettled()関数を使用して、すべてのプロミスを同時に開始することができます。例えば、data.tsではfetchCardData()関数でPromise.all()を使っています:

Goog to know:
Promise.allSettled()を使用すると、各プロミスの結果を status と value (または reason)キーを持つオブジェクトの配列として返すこともできる。エラーをより優雅に処理したい場合に便利です。

このパターン(Promise.allPromise.allSettled)を使うと、以下の利点があります。

  1. 全てのデータフェッチを同時に開始できるため、パフォーマンスが向上する可能性があります。
  2. これはJavaScriptのネイティブな機能であるため、どんなライブラリやフレームワークにも適用できます。

ただし、このパターンを使う時の欠点もあります。それは、もし一つのデータリクエストが他のリクエストよりも遅い場合、全てのリクエストが完了するのをその遅いリクエストが終わるまで待たなければならないということです。
これにより、実際には速く処理できるはずのリクエストも遅れてしまう可能性があります。

脚注
  1. エンドポイント ≒ 「URL+CRUD操作」 ↩︎

  2. ORM: ↩︎

ゆーとゆーと

memo

純粋な関数とコンポーネント関数の主な違いは、その関数がUIの一部を返すかどうかにあります。

純粋な関数 (Pure Function):

  • パラメータを入力として受け取り、計算やデータ処理を行い、結果を返します。
  • 副作用を持たず、同じ入力に対しては常に同じ出力を返します。
  • DOMを操作するためのものではなく、UIを生成したり操作したりすることはありません。
  • UIの一部を返す JSX や HTML マークアップを含まない。

コンポーネント関数 (Component Function):

  • React コンポーネントであるため、通常は JSX を返します。
  • props というオブジェクトをパラメータとして受け取ることが多く、これを使用して子コンポーネントにデータを渡したり、UIを構築したりします。
  • ライフサイクルフック(例:useState, useEffect など)を利用することがあります。
  • DOMに対する副作用を持つことがあります(例:状態を変更する、イベントハンドラを設定するなど)。

コードの中で確認する場合、関数が JSX を返していればそれは React コンポーネントの可能性が高いです。また、ファイル内で React のフックを使用していたり、props を受け取っている場合は、ほぼ間違いなくコンポーネントです。

以下は純粋な関数の例です:

typescript
Copy code
export function add(a: number, b: number): number {
  return a + b;
}

そして、以下はコンポーネント関数の例です:

Copy code
export function WelcomeMessage(props: { name: string }) {
  return <h1>Hello, {props.name}!</h1>;
}

純粋な関数では任意のデータ型を返すことができますが、コンポーネント関数では React のコンポーネントとして解釈される JSX エレメントを返す必要があります。


Promise.allPromise.allSettled は、どちらも複数のプロミスを同時に処理するために使用される関数ですが、それぞれ異なる動作をします。

Promise.all:

  • すべてのプロミスが「成功」した場合にのみ、結果の配列を返します。
  • 一つでもプロミスが「失敗」すると、Promise.all は直ちにそのエラーを返し、他のプロミスの結果は無視されます。
  • 全てのタスクが成功することが必須で、一つでも失敗すると問題となるケースで使用します。

Promise.allSettled:

  • すべてのプロミスが完了するのを待ちます。プロミスが「成功」しても「失敗」しても、それぞれのプロミスの結果は配列に格納されます。
  • 各プロミスの結果が status キーで「fulfilled」または「rejected」を示し、valuereason を含むオブジェクトとして返されます。
  • 各タスクが成功したか失敗したかを個別に処理したい場合に使用します。すべてのプロミスの完了を待ち、それぞれに対して独自のエラーハンドリングを行うことができます。

つまり、Promise.all は全てが成功することが期待される時に使い、一つでも失敗すると全体が失敗するとみなします。対して、Promise.allSettled は各プロミスの成功・失敗にかかわらず、全体の完了を待ち、個別の結果に基づいて対応を行いたいときに使います。

ゆーとゆーと

Chapter 8 / Static and Dynamic Rendering

What is Static Rendering?

スタティックレンダリングとは、サーバー上でデータの取得とページのレンダリングをビルド時(デプロイ時)またはデータ更新時にあらかじめ行い、その結果をCDN(コンテンツ配信ネットワーク)にキャッシュ(保存)しておく方法です。

ユーザーがアプリケーションを訪れた時には、そのキャッシュされた結果が提供されます。スタティックレンダリングのメリットは以下のとおりです。

  • ウェブサイトが高速になる: あらかじめレンダリングされたコンテンツはキャッシュされるため、世界中のユーザーがより速く、信頼性の高い形でウェブサイトのコンテンツにアクセスできます。
  • サーバーの負荷が減る: コンテンツがキャッシュされるため、サーバーは各ユーザーのリクエストごとに動的にコンテンツを生成する必要がありません。
  • SEO(検索エンジン最適化)が向上する: レンダリングされたコンテンツは検索エンジンのクローラーがインデックスを作りやすくなります。これはページのロード時にコンテンツがすでに利用可能であるため、検索エンジンのランキングが向上する可能性があります。

スタティックレンダリングは、データがないUIやユーザー間で共有されるデータ(例えば、静的なブログ記事や製品ページ)に適しています。しかし、定期的に更新されるデータを持つダッシュボードなどには適していないかもしれません。

スタティックレンダリングの対極にあるのが、動的レンダリングです。

What is Dynamic Rendering?

動的レンダリングとは、ユーザーがページを訪れるたびにサーバー上でコンテンツを生成する方法です。動的レンダリングのメリットは以下の通りです。

  • リアルタイムデータ: 動的レンダリングにより、アプリケーションがリアルタイムまたは頻繁に更新されるデータを表示することができます。これはデータが頻繁に変わるアプリケーションに理想的です。
  • ユーザー特有のコンテンツ: 動的レンダリングを使えば、ユーザーの操作に基づいて更新されるデータを持つ、パーソナライズされたダッシュボードやユーザープロファイルなど、ユーザー特有のコンテンツを提供しやすくなります。
  • リクエスト時の情報: 動的レンダリングを利用すると、クッキーやURLの検索パラメーターなど、リクエスト時にのみわかる情報にアクセスすることができます。

簡単に言うと、動的レンダリングはユーザーごとに最新の情報を提供する方法で、リアルタイムのデータや個人に合わせた内容を表示する場合に適しています。


Simulating a Slow Data Fetch

ダッシュボードをダイナミックにすることは良い第一歩だ。
しかし...前の章で述べた問題がまだある。もし一つのデータリクエストが他の全てのリクエストより遅かったらどうなるでしょうか?
そこで、開発者が解決しなければならない共通の課題に行き着く。

Dynamic Renderingでは、アプリケーションは最も遅いdata fetchと同じ速度しか出せない。

ゆーとゆーと

Chapter 9 / Streaming

前の章では、ダッシュボード・ページをダイナミックにしましたが、データ・フェッチの遅さがアプリケーションのパフォーマンスにどのような影響を与えるかについて説明しました。
ここでは、遅いデータリクエストがあったときにユーザーエクスペリエンスを改善する方法を見ていきましょう。


What is streaming?

ストリーミングとは、ルートを小さな "Chuncks "[1]に分割し、準備ができ次第、サーバーからクライアントに順次ストリーミングするデータ転送技術である。

ストリーミングを使用すると、データの読み込みが遅いことがページ全体の表示を遅らせるのを防ぐことができます。これにより、ユーザーはページの一部分を、全てのデータが読み込まれるのを待たずに見たり操作したりすることができます。

データの取得とレンダリングが同時に始まるため、ユーザーは準備ができ次第、UIを見ることができます。これは従来のウォーターフォール方式とは異なります。ウォーターフォール方式では、データの取得とレンダリングが順番に始まり、全てのデータが準備できるまでUIの表示がブロックされます。

ストリーミングはReactのコンポーネント・モデルと相性がよく、各コンポーネントは"チャンク"とみなすことができるからだ。

Next.jsでストリーミングを実装する方法は2つあります。

  1. ページレベルで、loading.tsxファイルを使用する。
  2. 特定のコンポーネントに<Suspense>を使用する方法です。


Streaming a whole page with loading.tsx

app/dashboardフォルダに、loading.tsxという新しいファイルを作成する:

ここではいくつかのことが起きている。

  1. loading.tsx はNext.jsで用いられる特別なファイルで、ReactのSuspenseという機能をベースにしています。これを使うと、ページのコンテンツが読み込まれる間に表示するためのローディングUIを作成できます。

  2. <Sidebar>は静的なコンポーネントなので、すぐに表示されます。これにより、ユーザーはページの他の動的な内容が読み込まれている間でも、<Sidebar>と対話することができます。

  3. ユーザーはページの読み込みが完了するのを待たずに、他のページへの移動を開始できます。これを「interruptable nabigation(中断可能なナビゲーション)」と呼びます。

しかし、ユーザー体験を向上させるためにもっとできることがあります。Loading...というテキストの代わりにスケルトンを表示してみましょう。


Adding loading skeletons

loading skeletonsは、UIの簡易版です。
多くのウェブサイトでは、コンテンツがロード中であることをユーザーに示すためのプレースホルダー[2]として使用しています。
あなたがloading.tsxに埋め込む全てのUIは、静的ファイルの一部として埋め込まれ、最初に送信されます。
そして、残りの動的コンテンツは、サーバーからクライアントにストリーミングされます。


Fixing the loading skeleton bug with route groups

現在、loading skeletonsはinvoicesとcustomersページにも適用されます。

loading.tsxはファイルシステム上、/invoices/page.tsx/customers/page.tsxよりも上位にあるため、これらのページにも適用されます。

これはRoute Groupsで変更できます。
dashboardフォルダ内に/(overview)という新しいフォルダを作成します。そして、loading.tsxとpage.tsxファイルをそのフォルダ内に移動します:

これで、loading.tsxファイルはダッシュボードの概要ページにのみ適用されます。

ルートグループを使用すると、URLパス構造に影響を与えることなく、ファイルを論理的なグループにまとめることができます。( ) を使用して新しいフォルダを作成すると、その名前は URL パスには含まれません。つまり、/dashboard/(overview)/page.tsx/dashboardになります。

ここでは、loading.tsxがダッシュボードの概要ページにのみ適用されるように、ルートグループを使っています。
しかし、ルートグループを使用して、アプリケーションをセクションに分けたり(例えば、(marketing)ルートと(dashboard)ルート)、大規模なアプリケーションのためにチームごとに分けることもできます。

Next.jsでは、ファイルシステムの構造をそのままURLのパスとして利用します。つまり、ファイルやフォルダの配置が直接ウェブサイトのURLに反映されるわけです。

しかし、ローディングのUIを特定のページ群にだけ適用したい場合があります。そこで役立つのがRoute Groupsです。

Route Groupsを作成するには、フォルダ名を括弧 () で囲みます。この括弧で囲まれたフォルダ名はURLには含まれません。
例えば、/dashboard/(overview)/page.tsx のURLは /dashboard となります。

この機能を使って、loading.tsxをダッシュボードの概要ページだけに適用させることができます。
これにより、/invoices/page.tsx/customers/page.tsx などの他のページには影響を与えずに、概要ページ専用のローディングUIを設定できるわけです。

簡単に言えば、ルートグループは、ウェブサイトの特定の部分にだけ特定のファイルや設定を適用するための便利な方法です。これにより、大規模なアプリケーションでチームごとやセクションごとにファイルを整理しやすくなります。


Streaming a component

ここまでは、ページ全体をストリーミングしています。
しかし、React Suspenseを使えば、もっと細かく、特定のコンポーネントをストリーミングすることができます。

Susppenseを使うと、何らかの条件が満たされるまで(例えばデータが読み込まれるまで)、アプリケーションの一部のレンダリングを延期できます。

例えば、あるデータの読み込みに時間がかかる場合、そのデータに依存するコンポーネントをSuspenseでラップし、そのコンポーネントがロードされる間に表示するフォールバック(代替)コンポーネントを設定します。

こうすることで、遅いデータリクエストがあっても、ページ全体の表示をブロックすることなく、他の部分のUIをすぐに表示させることができます。データを取得する処理をコンポーネント内部に移動させることで、特定のコンポーネントのみをストリーミングし、残りのページは即座に表示されるようにするのです。


Grouping components

ReactのSuspenseを使って<Card>コンポーネントをラップすることで、それぞれのカードのデータを個別に取得し、ロードします。
しかし、すべてのカードが一度に表示されない場合、カードが順番に現れる「ポップアップ効果」が発生し、ユーザーにとっては見た目が不快になる可能性があります。

これを解決するためには、カードをグループ化してより段階的な表示効果を作り出します。
具体的には、<Sidebar/>のような静的なコンポーネントを最初に表示し、次にグループ化したカードを表示させるラッパーコンポーネントを使用します。これにより、UIが一度に全て表示されるのではなく、順番に表示されることになり、ユーザーがページを見やすくなります。

/app/dashboard/page.tsx
// memo: defult のコンポーネントの場合は任意の名前でimportできる。
+ import CardWrapper from '@/app/ui/dashboard/cards';
// ...
export default async function Page() {
  return (
    <main>
      <h1 className={`${lusitana.className} mb-4 text-xl md:text-2xl`}>
        Dashboard
      </h1>
      <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
+         <Suspense fallback={<CardsSkeleton />}>
+           <CardWrapper />
+         </Suspense>
      </div>
      // ...
    </main>
  );
}


Deciding where to place your Suspense boundaries

Suspenseの境界線をどこに置くかは、いくつかの事柄による。

  • ユーザーにどのようにページを体験してもらいたいか。
  • どのコンテンツを優先するか。
  • コンポーネントがデータ・フェッチに依存している場合。


ダッシュボードのページを見て、もっと違ったやり方はなかったか?
心配しないでください。正解はありません。

  • loading.tsxで行ったように、ページ全体をストリーミングすることもできますが......コンポーネントの1つに遅いデータ・フェッチがある場合、ローディング時間が長くなる可能性があります。
  • 各コンポーネントを個別にストリーミングすることもできますが...その場合、UIが準備完了になると画面に飛び出す可能性があります。
  • ページセクションをストリーミングすることで、時差効果を作り出すこともできる。ただし、ラッパー・コンポーネントを作成する必要がある。


サスペンスの境界をどこに置くかは、アプリケーションによって異なります。
一般的には、データの取得を必要なコンポーネントに移し、そのコンポーネントをサスペンスでラップするのが良い方法です。
しかし、アプリケーションに必要であれば、セクションやページ全体をストリーミングしても問題はありません。

Susppenseは、より楽しいユーザー体験を生み出すための強力なAPIです。

脚注
  1. chuncks(チャンク):プログラミングやデータ処理の文脈では、大きなデータセットやファイルをより小さく扱いやすい部分に分割したものを指します。 ↩︎

  2. プレースホルダー:入力Form初期状態の薄いグレー文字とかのこと。 ↩︎

ゆーとゆーと

Chapter 10 / Partial Prerendering (Optional)

ゆーとゆーと

Chapter 11 / Adding Search and Pagination

次に検索とページネーションを追加する方法を学びましょう。


Starting code

サンプルコード貼り付け。

  • <Search/>は特定の請求書を検索することができます。
  • <Pagination/>は請求書のページ間の移動を可能にします。
  • <Table/>は請求書を表示します。

検索機能はクライアントとサーバーにまたがります。ユーザーがクライアント上でinvoiceを検索すると、URLパラメータが更新され、サーバー上でデータが取得され、新しいデータでテーブルがサーバー上で再レンダリングされます。


Why use URL search params?

上述したように、検索状態を管理するためにURL検索パラメータを使用することになります。
このパターンは、クライアント側の状態(例えばReactのstate)に慣れている人にとっては新鮮かもしれません。

URLパラメータを使って検索を実装することにはいくつかの利点があります:

  • ブックマークと共有が可能: 検索パラメータがURLに含まれるため、ユーザーは検索条件やフィルタを含んだアプリの状態をブックマークや共有することができます。
  • サーバーサイドレンダリングと初回読み込み: URLパラメータはサーバーで直接処理され、初期状態のレンダリングに使うことができるので、サーバーサイドレンダリングを扱うのが容易になります。
  • 分析と追跡: 検索クエリやフィルタがURLに直接含まれているため、ユーザー行動を追跡する際に追加のクライアント側ロジックが不要になります。

つまり、URLに検索情報を含めることで、ユーザビリティが向上し、開発者がアプリケーションの利用状況をより容易に理解し、改善することができるようになります。


Adding the search functionality

検索機能を実装するために使用するNext.jsクライアントフックは3つあります:

Next.jsでは、検索機能を実装するために役立つ、いくつかのクライアント側のフック(特別な関数)があります。

  1. useSearchParams: これを使うと、現在のURLのパラメータにアクセスできます。例えば、/dashboard/invoices?page=1&query=pendingというURLの場合、useSearchParams{page: '1', query: 'pending'}のように検索パラメータを取得できます。

  2. usePathname: 現在のURLのパス(ドメイン名の後の部分)を読み取ることができます。例として、/dashboard/invoicesというルートでusePathnameを使うと、'/dashboard/invoices'を返します。

  3. useRouter: これを使うと、プログラム上でクライアントコンポーネント間のルート(ページ)間を移動できます。使用できるメソッドは複数あります。

これらのフックを組み合わせることで、Next.jsのアプリ内で検索機能を実現し、URLの変化に応じてUIを更新したり、特定のページに移動したりすることができます。

ここで、導入ステップの概要を簡単に説明しよう:

  1. ユーザーの入力をキャプチャする。(Capture the user's input)
  2. 検索パラメータでURLを更新する。(Update the URL with the search params)
  3. URLを入力フィールドと同期させる。(Keeping the URL and input in sync)
  4. 検索クエリを反映するためにテーブルを更新する。(Updating the table)


1. Capture the user's input

新しいhandleSearch関数を作成し、<input>要素にonChangeリスナーを追加する。
onChangeは、入力値が変更されるたびにhandleSearchを呼び出します。


2. Update the URL with the search params

URLSearchParamsは、URLクエリ・パラメータを操作するためのユーティリティ・メソッドを提供するWeb APIです。複雑な文字列リテラルを作成する代わりに、?page=1&query=aのようなパラメータ文字列を取得するために使用することができます。

  • ${pathname}は現在のパスで、あなたの場合は"/dashboard/invoices "です。
  • ユーザーが検索バーに入力すると、params.toString()がこの入力をURLフレンドリーなフォーマットに変換します。
  • replace({pathname}?{params.toString()});コマンドは、ユーザーの検索データでURLを更新します。例えば、ユーザーが "lee "と検索した場合、/dashboard/invoices?query=leeとなります。
  • Next.jsのクライアントサイドナビゲーション(ページ間のナビゲーションの章で学びました)のおかげで、ページをリロードすることなくURLが更新されます。

「InputForm→URLパラメータ」


3. Keeping the URL and input in sync

入力フィールドがURLと同期し、共有時に入力されるようにするには、searchParamsから読み込んでinputにdefaultValueを渡します:

「URLパラメータ→InputForm」

defaultValue vs. value / Controlled vs. Uncontrolled

フォームの入力フィールドをどのように制御するかについてです。Reactでは、2つの方法で入力フィールドを管理できます。

  1. ** 制御コンポーネント(Controlled Component)**:
  • 入力フィールドのvalue属性を使用します。
  • Reactのstateを使って入力値を管理します。
  • 入力値の変更は全てReactが処理し、stateを更新することで入力フィールドの表示が変わります。
  1. 非制御コンポーネント(Uncontrolled Component):
  • 入力フィールドのdefaultValue属性を使用します。
  • 入力フィールドは独自の状態を持ち、Reactは直接管理しません。
  • これは検索クエリをReactのstateではなくURLに保存する場合に適しています。

検索クエリをReactのstateでなく、URLに保存しているため、defaultValueを使って非制御コンポーネントとして入力フィールドを扱っても問題ない。
つまり、入力フィールドに初期値を設定した後は、それをReactで管理する必要がなく、ユーザーが入力やURLの変更を通じて入力フィールドの状態を制御できるということ。


4. Updating the table

最後に、検索クエリを反映させるためにテーブル・コンポーネントを更新する必要がある。

PageコンポーネントはsearchParamsというpropを受け取るので、現在のURLパラメータを<Table>コンポーネントに渡すことができます。

❓ When to use the useSearchParams() hook vs. the searchParams prop?

クライアントコンポーネント内で検索パラメータを扱いたい場合は、useSearchParams() フックを使用します。このフックを使うと、サーバーにリクエストを送ることなく、クライアント側で直接URLの検索パラメータを読み取ることができます。
たとえば、ユーザーがページ上の検索バーに何かを入力したときにその値を取得するために使います。

サーバーコンポーネントでデータを取得する場合は、searchParams プロパティをページからコンポーネントに渡すことができます。
これは、サーバーサイドでレンダリングされる際にURLの検索パラメータを取得し、それをデータフェッチのロジックに使用するためです。

つまり、クライアント側で動的に検索パラメータを扱いたいときはフックを、サーバー側で初期レンダリング時にパラメータが必要なときはプロパティを使用する、というのが一般的な使い分け方になります。


Best practice: Debouncing

最適化するためにできることがあります。

検索バーに「Emil」と入力し、開発ツールのコンソールを確認してください。何が起こっていますか?

console
Searching... E
Searching... Em
Searching... Emi
Searching... Emil

キーストロークのたびにURLを更新しているので、キーストロークのたびにデータベースに問い合わせをしていることになります!
私たちのアプリケーションは小さいので、これは問題ではありませんが、もしあなたのアプリケーションに何千人ものユーザーがいて、それぞれがキーストロークのたびにデータベースに新しいリクエストを送信していたらと想像してみてください。

Debouncingは、関数が起動するレートを制限するプログラミング手法です。今回のケースでは、ユーザが入力を止めたときだけデータベースに問い合わせを行いたいとします。

デバウンスの仕組み

1.** Trigger Event**:デバウンスすべきイベント(検索ボックスのキー入力など)が発生すると、タイマーがスタートする。
2.** Wait**:タイマーが切れる前に新しいイベントが発生すると、タイマーがリセットされます。
3. ** Execution**:タイマーのカウントダウンが終了すると、デバウンス機能が実行される。

独自のデバウンス関数を手動で作成するなど、いくつかの方法でデバウンスを実装できます。
シンプルにするために、use-debounceというライブラリを使うことにする。

use-debounceをインストールします:
npm i use-debounce

<Search> コンポーネントに useDebouncedCallback という関数をインポートします:

app/ui/search.tsx
+ import { useDebouncedCallback } from 'use-debounce';

-   function handleSearch(term: string) {
+  const handleSearch = useDebouncedCallback((term) => {
        // ...
+ }, 300);

この関数は、handleSearchの内容をラップし、ユーザーが入力を止めてから特定の時間(300ミリ秒)後にのみコードを実行する。

もう一度検索バーを入力し、開発ツールのコンソールを開いてください。
デバウンスすることで、データベースに送られるリクエストの数を減らし、リソースを節約することができます。


Summary

URL ParamsとNext.js APIを使って、検索とページ分割を実装しました。
まとめると、この章では

  • クライアントstateの代わりにURL検索パラメータを使用して検索とページネーションを処理しました。
  • サーバーでデータを取得しました。
  • useRouterルータフックを使ってクライアントサイドの遷移をスムーズにした。

これらのパターンは、クライアントサイドのReactで作業するときに慣れ親しんでいるものとは異なりますが、URL検索パラメータを使用し、この状態をサーバーにリフティングすることの利点をよりよく理解していただけたと思います。




memo

  • Next.jsでは、特定のフォルダ以下のフォルダ名は特別な機能を持つ。page.tsxとか。
    NEXT.js / Docs

  • ?の言語の違い
    例えばDartでは、?を型の後に付けることで、その変数がnullを許容することを示す。
    例えば、オプショナル引数をvariable?として指定されない場合はnullとなる。
     
    一方で、JavaScriptではundefinedとnullは異なる値で、どちらも「値がない」ことを意味します。
    JavaScriptでは変数が宣言されただけで値が割り当てられていない場合、その変数はundefinedになります。関数のパラメーターが省略された場合もundefinedがデフォルトで割り当てられます。nullは開発者が明示的に割り当てる必要があります。
     
    たとえば、JavaScriptの関数でオプショナルなパラメータを指定した場合(function
    example(arg?) {...})、そのパラメータに値が渡されなかったときはundefinedになります。一方、Dartの同様のケース(void example(int? arg) {...})では、そのパラメータが省略されたときはnullになります。

ゆーとゆーと

Chapter 12 / Mutating Data

What are Server Actions

React Server Actionsは、サーバー側で非同期コードを直接実行する機能です。データ変更のためにAPIエンドポイントを作成する必要がなくなります。
代わりに、サーバー上で実行される非同期関数を書き、クライアントコンポーネントまたはサーバーコンポーネントから呼び出すことができます。

Server Actionsはセキュリティを高めるための方法です。
POSTリクエスト、暗号化されたクロージャ、厳格な入力チェック、エラーメッセージのハッシュ化、ホスト制限などの技術を使用して、さまざまな攻撃からアプリケーションを守ります。
これにより、データを保護し、権限のあるアクセスのみを確保し、アプリの安全性を大幅に向上させます。


Using forms with Server Actions

Reactでは、<form>要素のaction属性を使用してサーバーアクションを呼び出すことができます。
この方法で、フォームに入力されたデータが自動的にサーバーアクションに渡されます。

Reactでは、<form>要素のaction属性を使用してアクションを呼び出すことができます。actionは、取り込んだデータを含むnativeのformDataオブジェクトを自動的に受け取ります。

この方法の利点は、クライアント側でJavaScriptが無効になっていても、フォームが機能する「progressive enhancement」機能です。


Next.js with Server Actions

Server ActionsはNext.jsのcachingとも深く統合されています。
Server Actionsでフォームが送信されると、アクションを使用してデータを変更できるだけでなく、revalidatePathrevalidateTagなどのAPIを使用して、関連するキャッシュを再検証することもできます。


Creating an invoice

`use server'を追加することで、ファイル内にエクスポートされたすべての関数をサーバー関数としてマークすることができます。これらのサーバー関数は、クライアントコンポーネントやサーバーコンポーネントにインポートすることができます。

Good to know:
HTMLでは、<form>要素のaction属性にはフォームデータを送信する先のURLを指定しますが、Reactではactionを特別なpropとして扱います。APIを直接呼び出す代わりに、関数をactionプロップに渡すことができます。
サーバーアクションを使うと、バックエンドで自動的にPOST APIエンドポイントが生成されるため、手動でAPIエンドポイントを作成する必要がなくなります。
これにより、開発が簡単になり、コードの量も減ります。

Next.jsには、ルートセグメントをユーザーのブラウザに一時的に保存するClient-side Router Cacheがあります。 prefetchingとともに、このキャッシュは、サーバーへのリクエスト数を減らしながら、ユーザーがルート間をすばやく移動できるようにします。


Updating an invoice

Next.jsでは、データに基づいてページのパスを動的に作成したい場合に、Dynamic Route Segmentsを作ることができます。
たとえばブログの投稿タイトルや商品ページなどです。これを実現するために、フォルダ名を角括弧で囲みます。例えば、[id][post][slug]のようにします。
これにより、URLの一部が変数のように機能し、さまざまな値をその場所に挿入して、多様なパスに対応できるようになります。

直接actionに引数を渡すことはできない。

<form action={updateInvoice(id)}> // NG

bind関数で変形してから渡すことができる。

 const updateInvoiceWithId = updateInvoice.bind(null, invoice.id);


memo

formタグでactions={function}を指定。
inputやButtonタグのtype="submit"とかのイベント時に指定したactionが呼び出される。
actionには自動でinputのForm情報が渡る。

ゆーとゆーと

Chapter 13 / Handling Errors

error.tsx ファイルは、ルートセグメントの UI 境界を定義するために使用できます。
これは、何か問題が起こった際に、ユーザーが見ることになるエラー画面やメッセージを定義する場所として機能します。

error.tsxだと全てのエラーを拾うので、特定のエラーだけを拾いたい場合はnot-found関数とnot-found.tsxを使用する。
notFoundはerror.tsxよりも優先されるので、より具体的なエラーに対処したい場合は、notFoundを使うことができる。

memo

ルートセグメントとは、URL内の特定の部分を指す用語です。
Webアプリケーションにおいて、ルートはユーザーがアクセスするパスやURLの一部を指し、セグメントはそのパスの個々の区切られた部分を意味します。
例えば、/products/shoesというURLがあったとき、productsとshoesはそれぞれ個別のルートセグメントとなります。
これにより、アプリケーションはURLの構造に基づいて異なるページやデータを表示することができます。