ShotaroHirose59 / nextjs-dashboard

Learn Next.js
https://nextjs-dashboard-dun-pi-28.vercel.app
0 stars 0 forks source link

Chapter9 Streaming #14

Open ShotaroHirose59 opened 3 months ago

ShotaroHirose59 commented 3 months ago

In the previous chapter, you made your dashboard page dynamic, however, we discussed how the slow data fetches can impact the performance of your application. Let's look at how you can improve the user experience when there are slow data requests. https://nextjs.org/learn/dashboard-app/streaming

Topics

key word

ストリーミングという言葉に対してまだあまり理解できていないかも。 内容はわかるんだろうけど。

loading skeletons、これ案件のPRに単語として出てきたな。

ShotaroHirose59 commented 3 months ago

What is streaming?

ストリーミングは、ルートをより小さな「チャンク」に分割し、準備が整ったらサーバーからクライアントに段階的にストリーミングできるデータ転送技術です。

スクリーンショット 2024-03-16 11 02 23

ストリーミングすることで、遅いデータ要求によってページ全体がブロックされるのを防ぐことができます。これにより、ユーザーは、UI がユーザーに表示される前にすべてのデータが読み込まれるのを待たずに、ページの一部を表示して操作できるようになります。

スクリーンショット 2024-03-16 11 04 59

ユーザーはページ全体が準備されるのを待たずに、アプリを利用することができる

キーワード

チャンク

言葉としての意味は大きな塊 各コンポーネントはチャンクとみなすことができる。 なので、ストリーミングReactコンポーネントと上手く連携する

ハイドレーションとは

生成されたそれぞれの HTML には、そのページの生成に最低限必要な JavaScript コードが関連事項づけられています。 ページがブラウザから読み込まれると、JavaScript コードが走りページをインタラクティブなものにします。(この処理はハイドレーションと呼ばれています。) プリレンダリング

以下のコンポーネントを表示するとする。

import { useState } from "react";

function Hello() {
  const [count, setCount] = useState(0);
  return (
    <>
      <div>{count}</div>
      <button onClick={() => setCount(() => count + 1)}>click here!</button>
    </>
  );
}

export default Hello;

Next.jsではコンポーネントをプリレンダリング(事前にサーバーでHTMLを生成)する。 ブラウザ側でハイドレーションが実行され、onClickイベントが動作するようになります。

ちなみに、Reactの場合は以下のようになる。

<div id="root"></div>

空のdivの要素内にコンポーネント等の要素がクライアント側で追加されていく流れになる。

ShotaroHirose59 commented 3 months ago

ページ全体をストリーミングするloading.tsx

loading.tsxではページ全体をストリーミングする。(layout.tsxはストリーミングしない)

loading.tsxを実行すると以下のようなことが起こる

  1. loading.tsxは Suspense 上に構築された特別な Next.js ファイルで、ページ コンテンツの読み込み中に代替として表示するフォールバック UI を作成できます。
  2. は静的であるためすぐに表示されます。ユーザーは、動的コンテンツの読み込み中にと対話できます。
  3. ユーザーは、ページの読み込みが完了するまで待つ必要はありません (これを中断可能なナビゲーションと呼びます)。

Adding loading skeletons

A loading skeleton is a simplified version of the UI Many websites use them as a placeholder (or fallback) to indicate to users that the content is loading.

スケルトンはUI・UX的にとても良いと思うので、取り入れて行きたいなー。 ただ実装コストはかけたくない。

実装中のこのアプリではTailwindで簡単なコードのみでスケルトンのアニメーションが実装されてる

const shimmer =
  'before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_2s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/60 before:to-transparent';

コンポーネントは自作する必要がある。

export default function DashboardSkeleton() {
  return (
    <>
      <div
        className={`${shimmer} relative mb-4 h-8 w-36 overflow-hidden rounded-md bg-gray-100`}
      />
      <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
        <CardSkeleton />
        <CardSkeleton />
        <CardSkeleton />
        <CardSkeleton />
      </div>
      <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
        <RevenueChartSkeleton />
        <LatestInvoicesSkeleton />
      </div>
    </>
  );
}

Mantineでも簡単にできるっぽい https://mantine.dev/core/skeleton/

アニメーションの動きを調整したい場合はTailwindの方がやりやすそう

Fixing the loading skeleton bug with route groups

loading.tsxは下位のルートにも影響を与える。invoices/page.tsxとcustomers/page.tsx。 これをこれはルートグループで変更できる。

スクリーンショット 2024-03-16 11 42 57

これで、loading.tsxファイルはダッシュボードの概要ページにのみ適用される。ルートグループを使用すると、URL パス構造に影響を与えることなく、ファイルを論理グループに編成できる

ShotaroHirose59 commented 3 months ago

Streaming a component

React Suspense を使用すると、より詳細に特定のコンポーネントをストリーミングできる。

サスペンスを使用すると、何らかの条件が満たされるまで (データがロードされるなど)、アプリケーションのレンダリング部分を延期できます。動的コンポーネントをサスペンスでラップできます。次に、動的コンポーネントのロード中に表示するフォールバック コンポーネントを渡します。

この場合、下位のコンポーネントでデータフェッチして、親のコンポーネントで下位のコンポーネントをSuspenseでラップする。

Grouping components

複数のコンポーネントを同時にロードしたい場合はコンポーネントをグループ化する。

ex:

export default async function CardWrapper() {
  const {
    numberOfInvoices,
    numberOfCustomers,
    totalPaidInvoices,
    totalPendingInvoices,
  } = await fetchCardData();

  return (
    <>
      <Card title="Collected" value={totalPaidInvoices} type="collected" />
      <Card title="Pending" value={totalPendingInvoices} type="pending" />
      <Card title="Total Invoices" value={numberOfInvoices} type="invoices" />
      <Card
        title="Total Customers"
        value={numberOfCustomers}
        type="customers"
      />
    </>
  );
}

CardWrapperコンポーネントを親のコンポーネントでSuspenseでラップする

ShotaroHirose59 commented 3 months ago

Deciding where to place your Suspense boundaries

サスペンスの境界をどこに置くかは以下の要素によって判断される

  1. How you want the user to experience the page as it streams.
  2. What content you want to prioritize.
  3. If the components rely on data fetching.

以下のような実装パターンがある。

サスペンス境界をどこに置くかは、アプリケーションによって異なります。一般に、データのフェッチを必要なコンポーネントまで移動し、それらのコンポーネントを Suspense でラップすることをお勧めします。ただし、アプリケーションが必要とする場合は、セクションまたはページ全体をストリーミングしても問題はありません。

一般に、データのフェッチを必要なコンポーネントまで移動

ダッシュボードのように複数データを扱うパターンでもそうだし、 TODOリストのみを扱うようなページでもこうすべきな印象。

こうするとApp Router パターンではpage.tsxを親とする下位のコンポーネントもサーバーコンポーネントであると思うが、 コンポーネントファイルをどこに置くべきなのか悩ましいところ