Closed15

【key-front】Learn Next.jsで学んだことをChapterごとにまとめていく

1zushun1zushun

モチベーション

  • 毎週木曜日Slackのkey_frontチャンネルでハドル機能を使いお題に対してメンバー同士ディスカッションをする時間を15〜30分程度設けている
  • 今回はLearn Next.jsで学んだこと、取り組んだことについてまとめていく
  • ファシリテーターは筆者なので、事前に読み込んで気になった点などをスクラップに投げていく
  • 開催日は○○/○○(木)で最終的に議事録として結論をまとめる

参考記事

https://nextjs.org/learn

1zushun1zushun

Introduction

特になし

【Chapter 1】Getting Started

特になし

【Chapter 2】CSS Styling

特になし

【Chapter 3】Optimizing Fonts and Images

特になし

【Chapter 4】Creating Layouts and Pages

特になし

【Chapter 5】Navigating Between Pages

特になし

【Chapter 6】Setting Up Your Database

特になし

1zushun1zushun

【Chapter 7】Fetching Data

What are request waterfalls?

  • ウォーターフォールとは前のリクエストの完了に依存する一連のネットワークリクエストのこと。
  • データフェッチの場合、各リクエストは前のリクエストがデータを返して初めて開始できる。

なので下記のようなフェッチではfetchRevenuefetchLatestInvoicesfetchCardDataの順にフェッチが開始される

逆に一個前のフェッチから得られるデータ(例えばidとか)が次のフェッチに関係している場合などは有効である(がほとんどの場合では意図せずパフォーマンスに影響を与える)

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

Parallel data fetching

解決方法として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`;
 
    const data = await Promise.all([
      invoiceCountPromise,
      customerCountPromise,
      invoiceStatusPromise,
    ]);
    // ...
  }
}

メモ

  • 別にウォータフォール問題に関してはApp Routerとか関係なしに今React(Next.js)が直面している課題になる。
  • そのため上記の課題、解決策に関してもすでに多くの記事で取り扱われているもの。
  • fetch then renderfetch on renderrender as you fetchで検索すると多くヒットするはず
  • ウォータフォール問題の詳細を知りたい場合は下記のディスカッションを見る、別途スクラップにまとめる予定

https://github.com/reactwg/react-18/discussions/37

1zushun1zushun

【Chapter 8】Static and Dynamic Rendering

Making the dashboard dynamic

Chapterではdynamic renderingでunstable_noStoreを使っているが、vercel/postgresのsqlでフェッチしている(fetch関数を使っていない)からで、force-dynamic使うのもokとのこと。

vercel/postgresに限らずFirestoreからのフェッチなどfetch関数を用いないデータフェッチはあり得るので実装の際に念頭に置いておきたい。補足事項にも記載があった

unstable_noStore is an experimental API and may change in the future. If you prefer to use a stable API in your own projects, you can also use the Segment Config Option export const dynamic = "force-dynamic".

Simulating a Slow Data Fetch

メモ

1zushun1zushun

【Chapter 9】Streaming

  • Q, では遅いデータリクエストの時にUXを改善する方法は?
  • A, ストリーミングさせよう

Next.jsでストリーミングを実装するには、2つの方法がある

  • ページレベルで、loading.tsxファイルを使う
  • 特定のコンポーネントに対して、<Suspense>を使う。

Streaming a whole page with loading.tsx

loading.tsx is a special Next.js file built on top of Suspense, it allows you to create fallback UI to show as a replacement while page content loads.

  • loading.tsx is a special Next.js file built on top of Suspenseに関して認知していなかった。後で確認する。
  • つまりloading.tsxの配置でページ全体をストリーミングできていることになる。

Fixing the loading skeleton bug with route groups

  • route groupsのユースケースについて知れた。
  • 具体的にはlayout.tsxはネストしたpage.tsxに伝えたいけど、loading.tsxはネスト先に伝えたくない時。
  • 純粋に責務分けとしてroute groupsを使っていたけど、layout.tsxやloading.tsxの伝播を制御する時にも使えることを知れた。

Deciding where to place your Suspense boundaries

Where you place your suspense boundaries will vary depending on your application. In general, it's good practice to move your data fetches down to the components that need it, and then wrap those components in Suspense. But there is nothing wrong with streaming the sections or the whole page if that's what your application needs.

  • 一般的には、データ・フェッチを必要なコンポーネントに移し、そのコンポーネントをサスペンスでラップするのが良い方法だ。

メモ

  • ウォータフォール問題を解決するためにSuspense(Streaming)がある
1zushun1zushun

【Chapter 10】Partial Prerendering (Optional)

Chapter 10は飛ばす。

今後stableになりそうならキャッチアップする。

※部分プリレンダリングは、Next.js 14で導入された実験的な機能です。このページの内容は、この機能が安定するにつれて更新される可能性があります。

https://twitter.com/asidorenko_/status/1733172094517534981?s=12&t=0Bs_ltBYiO3nhiiL9YZAEw

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

1zushun1zushun

【Chapter 11】Adding Search and Pagination

Why use URL search params?

ReactのuseStateなどの状態管理でハンドリングできるのにURL paramsで検索機能を実装するかのアンサー。言われてみればで目から鱗だった。

  • Bookmarkable and Shareable URLs: Since the search parameters are in the URL, users can bookmark the current state of the application, including their search queries and filters, for future reference or sharing.
  • Server-Side Rendering and Initial Load: URL parameters can be directly consumed on the server to render the initial state, making it easier to handle server rendering.
  • Analytics and Tracking: Having search queries and filters directly in the URL makes it easier to track user behavior without requiring additional client-side logic.

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

なるほど、知らなかった。こういったサバコン用、クラコン用の関数はありそうなので後ほど調査する。

どちらを使うかは、クライアントで作業しているかサーバで作業しているかによって異なります。

next/navigation全体とdebounce、ページネーションを学べる良いチャプターだった。もし挑戦するならServer Actionsで無限スクロールにして試してみる。

1zushun1zushun

【Chapter 12】Mutating Data

What are Server Actions?

セキュリティ面にフォーカスして紹介されていた

Security is a top priority for web applications, as they can be vulnerable to various threats. This is where Server Actions come in. They offer an effective security solution, protecting against different types of attacks, securing your data, and ensuring authorized access. Server Actions achieve this through techniques like POST requests, encrypted closures, strict input checks, error message hashing, and host restrictions, all working together to significantly enhance your app's safety.

でもちゃんとプログレッシブエンハンスメントにも言及していた

An advantage of invoking a Server Action within a Server Component is progressive enhancement - forms work even if JavaScript is disabled on the client.

Creating an invoice 3. Extract the data from formData

知らなかった、便利。

If you're working with forms that have many fields, you may want to consider using the entries() method with JavaScript's Object.fromEntries(). For example:

const rawFormData = Object.fromEntries(formData.entries())

Creating an invoice 6. Revalidate and redirect

この一連の実装react-queryでやってたな、Next.jsが提供しているAPIだけで完結するのか。

Next.js has a Client-side Router Cache that stores the route segments in the user's browser for a time. Along with prefetching, this cache ensures that users can quickly navigate between routes while reducing the number of requests made to the server.

Since you're updating the data displayed in the invoices route, you want to clear this cache and trigger a new request to the server. You can do this with the revalidatePath function from Next.js:

Updating an invoice 4. Pass the id to the Server Action

you can pass id to the Server Action using JS bind. This will ensure that any values passed to the Server Action are encoded.

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

  return (
    <form action={updateInvoiceWithId}>
      <input type="hidden" name="id" value={invoice.id} /> // NOTE:こっちでもidを渡せる
    </form>
  );

フォームに隠し入力フィールドを使用することも可能だが値はHTMLソースにフルテキストとして表示されるため、IDのような機密データには不向き。

1zushun1zushun

【Chapter 13】Handling Errors

That's something to keep in mind, notFound will take precedence over error.tsx, so you can reach out for it when you want to handle more specific errors!

  • not-foundの方が優先される(not-found.tsx > error.tsx)
1zushun1zushun

【Chapter 14】Improving Accessibility

Using the ESLint accessibility plugin in Next.js

後でESListのNext.jsについて貼る

Server-Side validation

zod使ったServer-Side validationの説明がある

1zushun1zushun

【Chapter 15】Adding Authentication

Authentication vs. Authorization

Authentication is about making sure the user is who they say they are. You're proving your identity with something you have like a username and password.

Authorization is the next step. Once a user's identity is confirmed, authorization decides what parts of the application they are allowed to use.

  • 認証はあなたが誰であるかをチェックし、認可はあなたがアプリケーションで何ができるか、何にアクセスできるかを決定する。

Setting up NextAuth.js

https://vercel.com/docs/projects/environment-variables

Protecting your routes with Next.js Middleware

The advantage of employing Middleware for this task is that the protected routes will not even start rendering until the Middleware verifies the authentication, enhancing both the security and performance of your application.

Adding the Credentials provider

Credentials プロバイダを使うと、ユーザ名とパスワードでログインできるようになります。

一般的にはOAuthやメールプロバイダなどの代替プロバイダを使用することが推奨されています。

1zushun1zushun

Chapterごとのまとめ

  • Chapter1〜6までは環境構築がメインだった。
  • Chapter7〜9はSuspenseメインの説明でNext.jsが本来課題としているウォータフォール問題についてまとめられていた(RSCはJSバンドルを少なくするための別方向からのパフォーマンス改善のアプローチ)
  • Chapter10はstableになったら見返す予定
  • Chapter11でnext/navigationの全体をサーチフォーム、ページネーションの具体例を通して学べた。改造して無限ループにしてみる。
  • Chapter12はSever Actionsについて勉強することができた。正直App Routerのキャッチアップで後回しになっていたので取り上げられていて助かった。
  • Chapter13〜16はエラーハンドリングやa11y、メタデータなど後回しになりがちなところにも触れられていたのでドキュメントを見返すきっかけになった。特にChapter15ではNextAuthも取り上げられているのでサンプルとしてすごく密度が濃かった。

Learn Next.jsを実際にやってみての感想

Learn Next.jsを実際にやってみて、進め方が丁寧でわかりやすかった(コピペして動かす→動かした機能の説明→実践編の構成だった)のでハンズオンの勉強会資料としても参考にしようと思った。

あくまでApp RouterがメインなのでRSCについてまでは詳細に触れられていなかった。ブラックボックスにならないようにキャッチアップは別途必要だと思った。

各Chapterの肉付けは今後もしていく予定だが、一旦こちらのスクラップはクローズにします。

1zushun1zushun

【Chapter 7】Fetching Data 補足

下記を読んだメモ

https://github.com/reactwg/react-18/discussions/37

SSR

Server-side rendering (abbreviated to “SSR” in this post) lets you generate HTML from React components on the server, and send that HTML to your users. SSR lets your users see the page’s content before your JavaScript bundle loads and runs.

  • すごくコンパクトにSSRの概要とメリットを述べている

The key part is that each step had to finish for the entire app at once before the next step could start. This is not efficient if some parts of your app are slower than others, as is the case in pretty much every non-trivial app.

  • SSRのデメリット(どちらかというと弱点か)が述べられている。each step had to finish for the entire app at once before the next step could start. とは下記が該当する

React 18 lets you use <Suspense> to break down your app into smaller independent units which will go through these steps independently from each other and won’t block the rest of the app.

  • Suspenseを使うことで小さなユニットとして分割することができる

How can we solve these problems?

This is because there is a “waterfall”: fetch data (server) → render to HTML (server) → load code (client) → hydrate (client). Neither of the stages can start until the previous stage has finished for the app. This is why it’s inefficient. Our solution is to break the work apart so that we can do each of these stages for a part of the screen instead of entire app.

上記の日本語訳

データをフェッチ(サーバー)→HTMLにレンダリング(サーバー)→コードをロード(クライアント)→ハイドレート(クライアント)という「ウォーターフォール」があるからだ。どちらのステージも、アプリの前のステージが終了するまで開始できない。これが非効率的な理由だ。私たちの解決策は、作業を分割して、アプリ全体ではなく画面の一部分に対してこれらの各ステージを実行できるようにすることだ。

先ほどまでメモしていた内容の総括に思える。これがSSRのウォーターフォール問題に対するReactチームの回答と認識してよさそうな気もしなくもない。

We introduced the <Suspense> component for this purpose in 2018. When we introduced it, we only supported it for lazy-loading code on the client. But the goal was to integrate it with the server rendering and solve these problems.

  • 確かにReact.lazyとReact.Suspenseを使ってコード分割する目的で使っていた。ので「Suspenseを使ったらコード分割ができる」「フォールバックが使えるようになるのでisLoadingがいらなくなる」ではなく「SSRのウォーターフォール問題を解決する」と認識を改めた方が良いと思った。

  • 選択的ハイドレーションも説明されているので時間があったらまとめる

参考記事

次の記事がわかりやすい、同じディスカッションを参照していたので別角度から見ることができる

https://tech.anotherworks.co.jp/article/react-suspense-react18

このスクラップは4ヶ月前にクローズされました