Next.jsの理解を深める!Chapter 8-9
Chapter 8(Static Rendering)
What is Static Rendering?
With static rendering, data fetching and rendering happens on the server at build time (when you deploy) or during revalidation. The result can then be distributed and cached in a Content Delivery Network (CDN).
- 『静的レンダリング』は、ビルド時にデータをフェッチし、そのデータをもとにHTMLを生成するプロセス。生成されたHTMLは静的ファイルとして保存され、後からのリクエストに対して迅速に提供される。これによって、サーバーのレンダリングリソースが節約され、ページのロード時間が短縮される。Next.jsのデフォルトは『静的レンダリング』。
- 『Uncached Data Fetching』はユーザがそれぞれDBに対しリクエスト送ってレスポンス返ってくる為、データリソースがかかるし時間もかかるためパフォーマンスも悪い。それを避けるために『Cached Data Fetching』はキャッシュ層を挟む。取得データをキャッシュとして持たせることによってユーザはキャッシュからデータを取得できて、DBにアクセスされない為、データリソースを抑えられるしキャッシュはユーザの近いところ(エッジサーバー)で行われるためパフォーマンスも良い。
Static rendering is useful for UI with no data or data that is shared across users, such as a static blog post or a product page. It might not be a good fit for a dashboard that has personalized data that is regularly updated.
- 『静的レンダリング』は静的なブログ記事や製品ページなど、
データのないUIやユーザー間で共有されるデータに便利である。
Chapter 8(Dynamic Rendering)
What is Dynamic Rendering?
With dynamic rendering, content is rendered on the server for each user at request time (when the user visits the page). There are a couple of benefits of dynamic rendering:
Real-Time Data - Dynamic rendering allows your application to display real-time or frequently updated data. This is ideal for applications where data changes often.
User-Specific Content - It's easier to serve personalized content, such as dashboards or user profiles, and update the data based on user interaction.
Request Time Information - Dynamic rendering allows you to access information that can only be known at request time, such as cookies or the URL search parameters.
- 『動的レンダリング』はユーザーがページにアクセスした時(リクエスト時)に、
そのユーザーのコンテンツがサーバー上でリアルタイムにレンダリングされる。 - 『動的レンダリング』を使用すると、サーバーはリクエスト時に送信されるCookie、
URLの検索パラメータ、HTTPヘッダーなどの情報に基づき、動的なコンテンツを生成できる。
Making the dashboard dynamic
By default, @vercel/postgres doesn't set its own caching semantics. This allows the framework to set its own static and dynamic behavior.
You can use a Next.js API called unstable_noStore inside your Server Components or data fetching functions to opt out of static rendering. Let's add this.
In your data.ts, import unstable_noStore from next/cache, and call it the top of your data fetching functions:
- サーバーコンポーネントやデータフェッチ関数内で
unstable_noStore
というNext.jsのAPIを使用して、静的レンダリングを避けることができる。
// ...
import { unstable_noStore as noStore } from 'next/cache';
export async function fetchRevenue() {
// Add noStore() here to prevent the response from being cached.
// This is equivalent to in fetch(..., {cache: 'no-store'}).
noStore();
// ...
}
Note: 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".
-
unstable_noStor
は実験的なAPIであり、将来変更される可能性がある。安定したAPIを使いたい場合は、セグメント設定オプションのexport const dynamic = 'force-dynamic'
を使用することもできる。(セグメントごとにいろんなオプションを指定できる)
Chapter 9(loading.tsx)
What is streaming?
Streaming is a data transfer technique that allows you to break down a route into smaller "chunks" and progressively stream them from the server to the client as they become ready.
By streaming, you can prevent slow data requests from blocking your whole page. This allows the user to see and interact with parts of the page without waiting for all the data to load before any UI can be shown to the user.
Streaming works well with React's component model, as each component can be considered a chunk.
There are two ways you implement streaming in Next.js:
At the page level, with the loading.tsx file.
For specific components, with <Suspense>.
Let's see how this works.
- 『ストリーミング』は、データを小さな単位(チャンク)に分割して、準備ができたものから順にクライアントに送信する技術。この方法を使うことで、一つのデータリクエストが遅い場合でも、ページ全体がロードを待たずに部分的に表示され、ユーザーがすぐにページの一部と対話できる。
- 『ストリーミング』を実装する方法は2つあって
loading.tsx
or<Suspense>
を使用する。
Streaming a whole page with loading.tsx
In the /app/dashboard folder, create a new file called loading.tsx:
export default function Loading() {
return <div>Loading...</div>;
}
A few things are happening here:
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.
Since <SideNav> is static, it's shown immediately. The user can interact with <SideNav> while the dynamic content is loading.
The user doesn't have to wait for the page to finish loading before navigating away (this is called interruptable navigation).
Congratulations! You've just implemented streaming. But we can do more to improve the user experience. Let's show a loading skeleton instead of the Loading… text.
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. Any UI you embed into loading.tsx will be embedded as part of the static file, and sent first. Then, the rest of the dynamic content will be streamed from the server to the client.
Inside your loading.tsx file, import a new component called <DashboardSkeleton>:
import DashboardSkeleton from '@/app/ui/skeletons';
export default function Loading() {
return <DashboardSkeleton />;
}
Fixing the loading skeleton bug with route groups
Right now, your loading skeleton will apply to the invoices and customers pages as well.
Since loading.tsx is a level higher than /invoices/page.tsx and /customers/page.tsx in the file system, it's also applied to those pages.
We can change this with Route Groups. Create a new folder called /(overview) inside the dashboard folder. Then, move your loading.tsx and page.tsx files inside the folder:
-
loading.tsx
を使ったらNext.jsの内部的にはSuspense
に変換される。そのSuspense
がページのコンテンツを読み込み中に代替として表示するフォールバック UI を作成する。 -
loading skeleton
はUIの簡略化されたバージョン。多くのウェブサイトは、コンテンツがロード中であることをユーザーに示すためにこれらをプレースホルダー(またはフォールバック)として使用する。loading.tsx
に埋め込んだ任意のUIは静的ファイルの一部として埋め込まれ最初に送信される。その後、残りの動的コンテンツはサーバーからクライアントへストリームされる。 -
Route Groups
を使って以下のようなディレクトリ構成とするとloading skeleton
をdashboardにのみ適用できる。
Chapter 9(Suspense)
Streaming a component
So far, you're streaming a whole page. But, instead, you can be more granular and stream specific components using React Suspense.
Suspense allows you to defer rendering parts of your application until some condition is met (e.g. data is loaded). You can wrap your dynamic components in Suspense. Then, pass it a fallback component to show while the dynamic component loads.
If you remember the slow data request, fetchRevenue(), this is the request that is slowing down the whole page. Instead of blocking your page, you can use Suspense to stream only this component and immediately show the rest of the page's UI.
-
loading.tsx
を使うとページ全体をストリーミングする。しかし、
Suspense
を使うと特定のコンポーネントをもっと細かくストリーミングできる。 -
Suspense
を使うと、何らかの条件(例えば、データがロードされる)が満たされるまでアプリの一部のレンダリングを遅延させることができる。動的コンポーネントをSuspense
でラップし、
動的コンポーネントがロードされている間に表示するフォールバックコンポーネントを渡せる。 -
Suspense
を使うとそのコンポーネントのみをストリーミングし、
すぐにページの残りのUIを表示できる。
import { Card } from '@/app/ui/dashboard/cards';
import RevenueChart from '@/app/ui/dashboard/revenue-chart';
import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
import { lusitana } from '@/app/ui/fonts';
import { fetchLatestInvoices, fetchCardData } from '@/app/lib/data';
import { Suspense } from 'react';
import { RevenueChartSkeleton } from '@/app/ui/skeletons';
export default async function Page() {
const latestInvoices = await fetchLatestInvoices();
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();
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">
<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"
/>
</div>
<div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
<Suspense fallback={<RevenueChartSkeleton />}>
<RevenueChart />
</Suspense>
<LatestInvoices latestInvoices={latestInvoices} />
</div>
</main>
);
}