Next.js document全部読む
ルーティングファイル種類多いなー。全部使いこなしてる会社あるんだろうか
使いこなしたくなる気持ちもあるけど、どうなんだろう、ダルくならないか。。?ロックインされすぎてつらいってのはあるかも。
AppIcon、アプリのアイコンもここで定義できるってこと?アプリ開発したことないけど、どれくらい便利なのか聞いてみたい
pages routerの話は全部飛ばす
レイアウトが生のリクエストにアクセスするのを制限することで、Next.jsは、パフォーマンスに悪影響を及ぼす可能性のある、低速または高価なユーザーコードのレイアウト内での実行を防ぐことができます。
はえ〜
デフォルトでは、ページはサーバコンポーネントです。 paramsプロップを通してルートセグメントにアクセスでき、searchParamsプロップを通してURL検索パラメータにアクセスできます。
今描いてるコンポーネントがserver componentなのかclient componentなのかを勘違いする、みたいなことはめっちゃありそう
App Router上で構築された包括的なオープンソースのアプリケーションはありますか? はい。Next.js CommerceまたはPlatforms Starter Kitで、オープンソースのApp Routerを使用した2つの大規模な例をご覧いただけます。
たすかる〜
URLセグメント: スラッシュで区切られたURLパスの一部。 URLパス: ドメインの後に来るURLの一部(セグメントで構成)。
「セグメント」っていうワードはTutorialでも何度か見た希ガス
appディレクトリは、pagesディレクトリと並行して動作し、インクリメンタルな採用を可能にします。
はえ〜、と思ったけど、どちらかだけにするのは流石に鬼すぎるか
template Specialized re-rendered Layout UI
default Fallback UI for Parallel Routes
現状知らん子たち
わかりやすい。相変わらずtemplateはわかってないが
パラレルルート: 同じビューに2つ以上のページを同時に表示し、独立してナビゲートできるようにします。 独自のサブナビゲーションを持つ分割ビューに使用できます。 例:ダッシュボード
全然わかってない、キモそうという印象
ルートのインターセプト: ルートをインターセプトして、別のルートのコンテキストに表示することができます。 現在のページのコンテキストを維持することが重要な場合に使えます。 例えば、あるタスクを編集中にすべてのタスクを見たり、フィードの写真を拡大したりする場合などです。
これも全然わかってない
ルートセグメントをパブリックアクセスにするには、page.js ファイルが必要です。
開発中の画面は一応 _page.tsx
とかにしといて、prod環境で見えないようにする、みたいなことをやる可能性がある?(なさそう)
ページはデータをフェッチすることができます。 詳しくはデータ・フェッチ・セクションをご覧ください。
PPRをフル活用しようとすると、pageコンポーネントでデータフェッチする機会が減りそう
ルートレイアウトはアプリディレクトリのトップレベルで定義され、すべてのルートに適用されます。 このレイアウトは必須で、htmlタグとbodyタグを含まなければならず、サーバーから返される最初のHTMLを修正できます。
必須、まぁそうか
ルートレイアウトにのみ、<html>タグと<body>タグを含めることができます。
レイアウトはデフォルトではサーバーコンポーネントですが、クライアントコンポーネントに設定することもできます。
はえ〜
すべてのルートセグメントにアクセスするには、クライアントコンポーネントで useSelectedLayoutSegment または useSelectedLayoutSegments を使用します。
全然知らん子、パンクズとかを作る時に使うのか?
ルートにまたがって永続し、状態を維持するレイアウトとは異なり、テンプレートはナビゲーションの際に子ごとに新しいインスタンスを作成します。 つまり、ユーザがテンプレートを共有するルート間をナビゲートすると、子の新しいインスタンスがマウントされ、DOM要素が再作成され、クライアントコンポーネントでは状態が保持されず、エフェクトが再同期されます。
このような特定の動作が必要な場合、レイアウトよりもテンプレートの方が適している場合があります。 例:ナビゲーション時にuseEffectを再同期させる。 ナビゲーション時に子クライアントコンポーネントの状態をリセットする。
なるほど〜
In terms of nesting, template.js is rendered between a layout and its children. Here's a simplified output:
<Layout>
{/* Note that the template is given a unique key. */}
<Template key={routeParam}>{children}</Template>
</Layout>
Reactコンポーネントにkeyを付与して強制的にre-renderさせるアレか
となると、pageコンポーネントは毎回再描画されるから、React.memoを使ったコンポーネントのメモ化をちゃんとやらないと再描画が頻発する?
React18だか19だかでReactComponentのメモ化周りの管理が楽になる的な噂を聞いた気がする、もしかしたらそれでいい感じにアレをアレしてくれるのかも?
メタデータは、layout.jsまたはpage.jsファイルにメタデータ・オブジェクトまたはgenerateMetadata関数をエクスポートすることで定義できます。
ルート・レイアウトに<title>や<meta>のような<head>タグを手動で追加すべきではありません。 代わりに、ストリーミングや<head>要素の重複除去といった高度な要件を自動的に処理するMetadata APIを使用してください。
( ´_ゝ`)フーン
redirectは内部的にエラーを投げるので、try/catchブロックの外でコールする必要があります。
redirectはレンダリング処理中のクライアントコンポーネントでコールできますが、イベントハンドラではコールできません。
レンダリング処理の前にリダイレクトしたい場合は、next.config.jsかMiddleware.Config.jsを使ってください。
この辺りは知っとくといつか便利そう
Prefetching is not enabled in development, only in production.
うっかり勘違いしそう
Next.jsには、ルーターキャッシュと呼ばれるインメモリークライアントサイドキャッシュがあります。
挙動気になる。あとで詳しく出てくるとのこと
デフォルトでは、Next.jsは前後方向のナビゲーションのスクロール位置を維持し、ルーターキャッシュのルートセグメントを再利用します。
地味に嬉しいやつ
pages/からapp/に段階的に移行する場合、Next.jsルーターは自動的に2つの間のハードナビゲーションを処理します。
はえ〜
ちんたら移行してるとUX棄損する可能性ありますよと
期待されるエラーを戻り値としてモデル化する: Server Actionsで予想されるエラーに対してtry/catchを使用することは避けてください。 useFormState を使用してこれらのエラーを管理し、クライアントに返します。
useFormState、知らない子
'use client'
import { useFormState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button>Sign up</button>
</form>
)
}
定義したserverActionを渡して、actionのstateへのアクセスができる、なるほど
チュートリアルで触ったuseActionStateとの違いが気になる
これらの例では、Next.js App Routerに同梱されているReactのuseFormStateフックを使用しています。 React 19を使用している場合は、代わりにuseActionStateを使用してください。 詳細はReactのドキュメントを参照してください。
なるほど、React19に対応するバージョンに上がったらdeprecatedになるのかな
Uncaught Exceptions
とりあえず app/error.js
もしくは app/global-error.js
を置いてれば最低限OK?
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
reset
が便利そう。reloadとの違いが気になるものの
Streaming Server Rendering - サーバーからクライアントへHTMLを段階的にレンダリングする。
SSR2だ
Selective Hydration - Reactは、ユーザーのインタラクションに基づいて、最初にインタラクティブにするコンポーネントに優先順位を付けます。 Suspenseの例と使用例については、Reactドキュメントを参照してください。
何言ってるかわからん、次リンク先読む
Next.jsは、generateMetadata内のデータ取得が完了するまで待ってから、UIをクライアントにストリーミングします。 これにより、ストリームされたレスポンスの最初の部分に<head>タグが含まれることが保証されます。
たすかる
ストリーミングはサーバーレンダリングなので、SEOには影響しません。 Googleのリッチリザルトテストツールを使えば、あなたのページがGoogleのウェブクローラーにどのように表示されるか、シリアライズされたHTML(ソース)を見ることができます。
?クローラーのページごとのクロールバジェットが枯渇したタイミングでスケルトンだらけだったらあかんくないか?SEOやるときに検証した方がよさそう
スケルトンからの復帰はクローラが判断してくれそうな気もするな
これも読んだ方がよさそう
Suspense対応データソースを実装するための要件は不安定で、文書化されていません。 データソースとSusppenseを統合するための公式APIは、Reactの将来のバージョンでリリースされる予定です。
珍妙なデータ取得をやろうとするとfallbackが表示されない、みたいなことがある?
startTransition
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
これはReactに、状態遷移は緊急ではないので、すでに公開されているコンテンツを隠すのではなく、前のページを表示し続けたほうがいいということを伝える。 これで、ボタンをクリックすると、Biographyがロードされるのを「待つ」ことになる:
サスペンス対応ルーターは、デフォルトでナビゲーションの更新をトランジションにラップすることが期待されている。
Nextだとどういうタイミングでsuspenseのfallbackに切り替わるのか気になる
インジケータを追加するには、startTransitionをuseTransitionに置き換えて、isPendingというブール値を与えます。 下の例では、トランジションが発生している間にウェブサイトのヘッダーのスタイルを変更するために使用しています:
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
はえ〜
トランジションの間、Reactはすでに公開されているコンテンツを隠さないようにする。 しかし、異なるパラメータを持つルートにナビゲートする場合、Reactに異なるコンテンツであることを伝えたいと思うかもしれません。 これをキーで表現することができる:
<ProfilePage key={queryParams.id} />
Next.jsのSuspenseのサポート外でsuspenseしようとしたときに注意したい
目に見える UI をフォールバックで置き換えると、違和感のあるユーザーエクスペリエンスが生まれます。 これは、更新によってコンポーネントがサスペンドし、最も近いサスペンス境界がすでにユーザーにコンテンツを表示している場合に発生します。 これを防ぐには、startTransitionを使用して更新を緊急でないものとしてマークします。 トランジションの間、Reactは不要なフォールバックが表示されないよう、十分なデータがロードされるまで待機します:
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
たしかに便利かも
「Reactは、ユーザーのインタラクションに基づいて、最初にインタラクティブにするコンポーネントに優先順位を付けます。 Suspenseの例と使用例については、Reactドキュメントを参照してください。」に該当する項目なくない?
これらの改善は実質的なもので、数年にわたる作業の集大成です。 これらの改良のほとんどは舞台裏で行われるものですが、特にフレームワークを使用していない場合は、いくつかのオプトインのメカニズムについて知っておく必要があります。
頭が下がる
既存のAPIの主なものは<Suspense>です。
現状はとりあえずSuspenseの挙動を把握してればいいのかな
重要なのは、次のステップを開始する前に、各ステップがアプリ全体に対して一度に終了しなければならなかったことだ。 React 18では、<Suspense>を使ってアプリを小さな独立したユニットに分割することができます。
PPRを支える技術って感じかな
画面表示時点での完全性を求めて全てをSSRするか、静的コンテンツを見せるスピードを重視して全てをCSRするか
-> これに対しての <Suspense >を使ったストリーミングというアプローチ
- React.lazyを使ったコンポーネントの遅延読み込み
- これまではSSRでは動作させづらかった
- が、React18では違うぜ
- Suspenseでラップすることで、hydrationの開始条件からSuspenseで囲われたコンポーネントのロード完了を外せるようになった
段階的にコードのロード・HTMLの返却・hydrationを行う、かつ各プロセスを分割してより早くインタラクティブなUIパーツを増やせる、という理解。
一方で、「Reactは、ユーザーのインタラクションに基づいて、最初にインタラクティブにするコンポーネントに優先順位を付けます。 」についての理解がまだない
これで、ナビバーとポストを含む最初のHTMLの後に、その両方をサーバーからストリーミングできるようになった。 しかし、これは水和にも影響を及ぼします。 両者のHTMLはロードされたが、コードはまだロードされていないとしよう:
次に、サイドバーとコメントの両方のコードを含むバンドルがロードされます。 Reactは、ツリーの早い段階で見つけたサスペンス境界(この例ではサイドバー)から始めて、両方のハイドレートを試みます:
しかし、ユーザーがコメント・ウィジェットを操作し始めたとしよう:
Reactは、クリックイベントのキャプチャフェーズ中にコメントを同期的にハイドレートする:
その結果、コメントはクリックを処理し、インタラクションに反応するぎりぎりのタイミングでハイドレイトされる。 その後、Reactが緊急に行うことがなくなったので、Reactはサイドバーをハイドレートする:
これで3つ目の問題は解決した。 選択的水分補給のおかげで、「何かとインタラクションするためにすべてを水分補給する」必要はない。 Reactは可能な限り早い段階ですべての水和を開始し、ユーザーとのインタラクションに基づいて画面の最も緊急な部分に優先順位を付けます。 アプリ全体にSuspensionを採用すると、境界がより細かくなると考えれば、Selective Hydrationの利点はより明白になります:
これかー、なるほど
module.exports = {
async redirects() {
return [
// Basic redirect
{
source: '/about',
destination: '/',
permanent: true,
},
// Wildcard path matching
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
恒久的なURL差し替えの例
リダイレクトはプラットフォームによって制限があります。 例えば、Vercelの場合、リダイレクト数は1,024個に制限されています。 大量のリダイレクト(1000以上)を管理するには、ミドルウェアを使ったカスタムソリューションの作成を検討してください。 詳しくはリダイレクトを大規模に管理するをご覧ください。
redirects runs before Middleware.
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
恒久的なリダイレクトはこっちか
Middleware runs after redirects in next.config.js and before rendering.
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
import { NextResponse, NextRequest } from 'next/server'
import { get } from '@vercel/edge-config'
type RedirectEntry = {
destination: string
permanent: boolean
}
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData && typeof redirectData === 'string') {
const redirectEntry: RedirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// No redirect found, continue without redirecting
return NextResponse.next()
}
外部ストレージにリダイレクト情報を保管してリダイレクトルールを管理する例
ルートグループは、フォルダー名を括弧で囲むことで作成できます。
(folderName)
適当に設計するとカオス化しやすそう
複数のルートレイアウトを作成するには、トップレベルのlayout.jsファイルを削除し、各ルートグループ内にlayout.jsファイルを追加します。 これは、アプリケーションをまったく異なるUIやエクスペリエンスを持つセクションに分割するのに便利です。 <html>タグと<body>タグをそれぞれのルートレイアウトに追加する必要があります。
この辺りは実装例を見ながら把握した方が理解が進みそう
トップレベルのlayout.jsファイルなしで複数のルートレイアウトを使用する場合、ホームページ.jsファイルはルートグループの1つに定義する必要があります。
せやろなぁ
複数のルート・レイアウトにまたがってナビゲートすると、(クライアントサイド・ナビゲーションとは対照的に)フルページ・ロードが発生します。 例えば、app/(shop)/layout.jsを使用している/cartから、app/(marketing)/layout.jsを使用している/blogに移動すると、全ページがロードされます。 これは複数のルート・レイアウトにのみ適用されます。
なるほど
コロケーション(colocation)とは、複数の企業やユーザーがデータセンターなどの施設内で、サーバーや通信機器などを共同で設置することです。(google AI)
co-locationだからそうか
これはpagesディレクトリとは異なり、pagesにあるファイルはすべてルートとみなされる。
プロジェクトファイルは _
から始まるディレクトリに配置するのが安全?
将来のNext.jsファイル規約との潜在的な名前の衝突の回避
これは嬉しみとして理解できる
フォルダ名の前に%5F(アンダースコアのURLエンコード形式)を付けることで、アンダースコアで始まるURLセグメントを作成できます
知見
Next.jsはモジュールパスエイリアスをサポートしており、深くネストされたプロジェクトファイル間のインポートの読み取りと維持を容易にします。
Twitterで「やめとけ」って言われてたやつか?
app/books/[id] みたいなやつか
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const slug = (await params).slug
return <div>My Post: {slug}</div>
}
params propはpromiseなので、値にアクセスするにはasync/awaitかReactのuse関数を使う必要があります。
バージョン14以前では、paramsはsynchronousなpropでした。 後方互換性を保つために、Next.js 15でも同期的にアクセスできますが、将来的にはこの動作は廃止される予定です。
Reactのuse関数も読めてないなー
generateStaticParams 関数を動的なルートセグメントと組み合わせて使うことで、リクエスト時にオンデマンドでルートを生成するのではなく、ビルド時に静的にルートを生成することができます。
SSGするときはこっちを使うと
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
キャッチオールセグメント ダイナミックセグメントは、[...folderName]という括弧の中に省略記号を追加することで、後続のセグメントをすべてキャッチオールするように拡張することができる。
例えば、app/shop/[...slug]/page.jsは、/shop/clothesにマッチするが、/shop/clothes/tops、/shop/clothes/tops/t-shirtsなどにもマッチする。
一定のルールで階層構造を持たせつつ、アプリケーション側でパラメータをよしなに読み分けて、ページは出し分けるがレイアウト等がほぼ同じ、みたいなパターンで使えるという理解
キャッチオールセグメントは、パラメータを二重の角括弧で囲むことで、オプションにすることができます: [例えば、app/shop/[[...slug]]/page.jsは、/shop/clothes、/shop/clothes/tops、/shop/clothes/tops/t-shirtsに加えて、/shopにもマッチします。
ECサイトとかだと以外と利用頻度高いのかな
パラレルルートでは、同じレイアウト内で1つまたは複数のページを同時に、または条件付きでレンダリングできます。 ダッシュボードやソーシャルサイトのフィードなど、アプリの非常に動的なセクションに便利です。
言ってることは分からんでも無いけど、複数の「ページ」としてUIを構築したいモチベーションがわかってない
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{team}
{analytics}
</>
)
}
思ってたよりも書きぶりはシンプル
スロットは通常のページコンポーネントと組み合わされ、ルートセグメントに関連付けられた最終ページを形成します。 このため、同じルートセグメントレベルでスタティックスロットとダイナミックスロットを別々に持つことはできません。 1つのスロットが動的であれば、そのレベルのスロットはすべて動的でなければならない。
ちょっと何言ってるかわかんない
default.jsファイルを定義することで、初回ロード時やページ全体のリロード時に、マッチしないスロットのフォールバックとしてレンダリングすることができます。
/settingsに移動すると、@teamスロットは/settingsページをレンダリングし、@analyticsスロットは現在アクティブなページを維持します。
パラレルルートを使う場合、スロットごとにルートセグメントの構成は同期する必要があり、URLごとにルートセグメントがないスロットがある場合はdefault.jsが描画されるってことか。
ますます使いたくねーの気持ちが増してゆく
パラレルルート使う必要あるか?
うーん、読んだけどモチベーションがイマイチわからんかった。また後日読み返したら納得度が上がるかも?
同じルートセグメントレベルでスタティックスロットとダイナミックスロットを別々に持つことはできません。 1つのスロットが動的であれば、そのレベルのスロットはすべて動的でなければならない。
ダイナミックスロットと、通常のルートセグメントを並べることができないってことか?
Intercepting routes can be defined with the (..) convention, which is similar to relative path convention ../ but for segments.
ユーザーの操作によってページの見せ方を変えるための機能という理解。ECっぽい
ページが更新されたときに、モーダルを閉じるのではなく、コンテキストを保持する。
一覧ページと詳細ページを行き来して情報収集するようなWEBサイトで、デフォルトでは画面遷移はさせたくない、みたいなケースで使えると。ECとか不動産とか?
historyが大変なことになりそうではあるが、、
ちょこちょこパラレルルートの話が出てくるなぁ、これ流行るんか。。?
これらはpagesディレクトリ内のAPI Routesに相当します。つまり、API RoutesとRoute Handlersを一緒に使う必要はありません。
ルートハンドラは、page.jsやlayout.jsと同様に、アプリディレクトリ内の任意の場所に入れ子にすることができます。
なるほど
サポートされていないメソッドが呼び出された場合、Next.jsは405 Method Not Allowedレスポンスを返します。
ですよね
export const dynamic = 'force-static' // レスポンスをキャッシュする
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
})
const data = await res.json()
return Response.json({ data })
}
page.jsと同じルートに、route.jsファイルを置くことはできません。
あれ、そうなの
API Routesもこんなファイル構成だった気が
export const revalidate = 60 // ISR的なことができる
export async function GET() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return Response.json(posts)
}
cookieの読み書き
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` },
})
}
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
headerのread/set
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
パスパラメータを取得する例
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> }
) {
const slug = (await params).slug // 'a', 'b', or 'c'
}
streamingを返却する例。なんとなくイメージできるが、streamingが技術的にどういうものなのかイマイチ理解できていない
import { openai } from '@ai-sdk/openai'
import { StreamingTextResponse, streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return new StreamingTextResponse(result.toAIStream())
}
xmlを返す例。sitemap.xmlとかで使える
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Next.js Documentation</title>
<link>https://nextjs.org/docs</link>
<description>The React Framework for the Web</description>
</channel>
</rss>`,
{
headers: {
'Content-Type': 'text/xml',
},
}
)
}
ルートセグメントコンフィグを設定できるらしい。設定項目についてはあとから出てきそう
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
パスの書き換え: リクエストプロパティに基づいてAPIルートやページへのパスを動的に書き換えることで、A/Bテスト、機能ロールアウト、レガシーパスをサポートします。
たしかに、ABテストで使えるのか、なるほど
matcher を使用すると、特定のパスで実行されるミドルウェアをフィルタリングできます。
tutorialで認証用に作ったmiddleware
export default NextAuth(authConfig).auth;
export const config = {
matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"],
};
URLパターンごとに処理を出し分ける例
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
分析サーバーにログを送信する例
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
next.configで、trailingSlashの削除を無効化できる
module.exports = {
skipTrailingSlashRedirect: true,
}
skipMiddlewareUrlNormalizeを使用すると、Next.jsのURL正規化を無効にして、直接訪問とクライアント遷移の処理を同じにすることができます。 いくつかの高度なケースでは、このオプションは、元のURLを使用することにより、完全な制御を提供します。
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
クライアント遷移の場合のリクエストの仕様に対する理解が足りてなさそう
ミドルウェアは現在、Edgeランタイムと互換性のあるAPIのみをサポートしています。 Node.js専用のAPIはサポートされていません。
なるほど、わかってないのであとで読み返す
このルート内のどこにも動的APIを使用していない場合、next build時に静的ページにプリレンダリングされます。 その後、増分静的再生を使用してデータを更新できます。
Dynamic APIsってのが何なのかわかってない。あとで出てきそう
ISR無効化のための設定
export const dynamic = 'force-dynamic'
特定のURLへのリクエストのレスポンスを複数箇所で使い回す際、fetch APIの呼び出しを隠蔽した関数を作成して使い回すことを推奨するらしい
import { notFound } from 'next/navigation'
interface Post {
id: string
title: string
content: string
}
async function getPost(id: string) {
let res = await fetch(`https://api.vercel.app/blog/${id}`)
let post: Post = await res.json()
if (!post) notFound()
return post
}
export async function generateStaticParams() {
let posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post: Post) => ({
id: post.id,
}))
}
export async function generateMetadata({ params }: { params: { id: string } }) {
let post = await getPost(params.id)
return {
title: post.title,
}
}
export default async function Page({ params }: { params: { id: string } }) {
let post = await getPost(params.id)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
fetch関数の実行結果がキャッシュされることから、例えば親エンティティのページに遷移した時点で子エンティティのデータ取得関数を呼び出しておくことで、実際にデータが要求された際の表示速度を上げるテクがあるとのこと。
やりすぎると通信量が増えてページ全体が重くなるかも
プリロードを高速化するためのアプローチとして、 React.cacheと 'server-only' を使った例
import { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
'server-only'を使うことで、クライアントコンポーネントがこのモジュールをインポートしようとするとビルドエラーで検知できる
React.cacheはuseMemoと似ているが、cacheはあくまでサーバーサイドでのキャッシュ機能を提供する関数。
React.cacheにはデータ再検証のオプションなどはないため、使うなら1時間おき・1日おきなどでデータを更新できるように、引数にキャッシュ期間に関するキーを渡すのがよいのかも?
Reactのtaint APIについての記述があったが、experimentalなので一旦無視する
Dynamic APIsってのが何なのかわかってない。あとで出てきそう
リクエストの内容に依存するAPIリクエストのこと?
Server ActionはReactの "use server "ディレクティブで定義できます。 このディレクティブを非同期関数の先頭に置くことで、その関数をServer Actionとしてマークすることができます。また、別のファイルの先頭に置くことで、そのファイルのすべてのエクスポートをServer Actionとしてマークすることができます。
サーバーコンポーネントは、インライン関数レベルまたはモジュールレベルの "use server" ディレクティブを使用することができます。 サーバーアクションをインライン化するには、関数本体の先頭に "use server" を追加します:
export default function Page() {
// Server Action
async function create() {
'use server'
// Mutate data
}
return '...'
}
サーバーコンポーネントである時点でサーバーアクションにならんのか
ふと気になった、以下
clientComponentを呼び出すコンポーネントをまとめて配置するclientComponent、みたいな、PresentationalContainerComponentみたいなやつが出てくる?
いや、 'use client'
'use server'
のディレクティブをちゃんと描けば良い感じになるのかな
PPRするならその辺りよしなに処理してほしい
formタグのactionにserverActionなりformActionなりを渡す時のbindの使い方
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
これ頻出っぽい
別の方法として、フォームの隠し入力フィールドとして引数を渡すこともできます(例:<input type="hidden" name="userId" value={userId} />)。 ただし、値はレンダリングされたHTMLの一部となり、エンコードされません。
.bindはServer ComponentsとClient Componentsの両方で動作します。 また、プログレッシブエンハンスメントにも対応しています。
See the React <form> docs for more information.
これすげー言われるので読まないと
requestSubmit()メソッドを使用して、プログラムでフォーム送信をトリガすることができます。 例えば、ユーザが ⌘ + Enter キーボードショートカットを使用してフォームを送信すると、onKeyDown イベントをリッスンできます:
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
地味にとても嬉しいやつだ、便利関数化して仕込みまくりたい
zod推されてるなー、デファクトになったんだろうか
これらの例では、Next.js App Routerに同梱されているReactのuseFormStateフックを使用しています。 React 19を使用している場合は、代わりにuseActionStateを使用してください。
useActionStateへの乗り換え忘れないようにしよう
ReactのuseOptimisticフックを使用すると、Server Actionの実行が終了する前に、レスポンスを待つのではなく、UIを楽観的に更新することができます:
楽観的なアップデート、SWRのmutationでも出てきたな、使いたくねぇ
'use client'
import { publishPost, saveDraft } from './actions'
export default function EditPost() {
return (
<form action={publishPost}>
<textarea
name="content"
onChange={async (e) => {
await saveDraft(e.target.value)
}}
/>
<button type="submit">Publish</button>
</form>
)
}
revalidateの方法
- revalidatePath
- revalidateTag
あとで出てきそう
cookie操作
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
const cookieStore = await cookies()
// Get cookie
cookieStore.get('name')?.value
// Set cookie
cookieStore.set('name', 'Delba')
// Delete cookie
cookieStore.delete('name')
}
デフォルトでは、サーバーアクションが作成され、エクスポートされると、パブリックなHTTPエンドポイントが作成されます。 つまり、サーバーアクションやユーティリティ関数がコード内の他の場所にインポートされなくても、パブリックにアクセス可能です。
セキュリティを向上させるために、Next.jsには次の機能が組み込まれています: Next.jsは、クライアントがサーバーアクションを参照したり呼び出したりできるように、暗号化された非決定論的なIDを作成します。 これらのIDは、セキュリティ強化のため、ビルド間で定期的に再計算されます。
デッドコードの排除: 未使用のサーバーアクション(IDによって参照される)は、サードパーティによるパブリックアクセスを避けるために、クライアントバンドルから削除されます。
IDはコンパイル時に作成され、最大14日間キャッシュされる。 IDは、新しいビルドが開始されるか、ビルド・キャッシュが無効化されたときに再生成される。 このセキュリティ改善により、認証レイヤーがない場合のリスクが軽減されます。 ただし、Server ActionsをパブリックHTTPエンドポイントと同様に扱う必要があります。
14日ごとにコンパイルする必要がある?
コンポーネント内部でサーバー アクションを定義すると、アクションが外部関数のスコープにアクセスできるクロージャが作成されます。 例えば、publish アクションは publishVersion 変数にアクセスできます:
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}
return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}
センシティブな値がクライアントに公開されるのを防ぐために、暗号化だけに頼ることはお勧めしません。 その代わりに、Reactのtaint APIを使用して、特定のデータがクライアントに送信されないようにする必要があります。
あ〜
これ読んだ。めっちゃ便利じゃん
概観をつかんだので https://nextjs.org/docs/app/building-your-application/data-fetching/fetching#preventing-sensitive-data-from-being-exposed-to-the-client に戻ってみる
import { queryDataFromDB } from './api'
import {
experimental_taintObjectReference,
experimental_taintUniqueValue,
} from 'react'
export async function getUserData() {
const data = await queryDataFromDB()
experimental_taintObjectReference(
'Do not pass the whole user object to the client',
data
)
experimental_taintUniqueValue(
"Do not pass the user's address to the client",
data,
data.address
)
return data
}
クッソ便利やん
Overwriting encryption keys (advanced)
複数のサーバーにNext.jsをデプロイするような構成だと、暗号化キーが異なるAPIにアクセスしてしまい、不整合が発生する可能性がある。
その場合は process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
に値を渡すことで解決できる。
VercelにデプロイされたNext.jsアプリケーションは自動的にこの処理を行う。
ISR, vercel上じゃなくても動くんだろうか、と思ったけど次の職場vercel採用してた。好き〜
interface Post {
id: string
title: string
content: string
}
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
// We'll prerender only the params from `generateStaticParams` at build time.
// If a request comes in for a path that hasn't been generated,
// Next.js will server-render the page on-demand.
export const dynamicParams = true // or false, to 404 on unknown paths
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }: { params: { id: string } }) {
const post: Post = await fetch(
`https://api.vercel.app/blog/${params.id}`
).then((res) => res.json())
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
revalidateで指定した秒数を経過したあと、次のリクエストが来ないとビルドされないっすよね。。?
これ制御したいなぁ
再バリデーション時間を長く設定することをお勧めします。 例えば、1秒ではなく1時間。 より精度が必要な場合は、オンデマンド再検証の使用を検討する。 リアルタイムのデータが必要な場合は、ダイナミックレンダリングへの切り替えを検討してください。
ですよね〜
新しい記事を投稿した時に posts 配下のページを全てrevalidateする方法
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the /posts route in the cache
revalidatePath('/posts')
}
postが増えるとビルド時間伸びそうなので、適宜revalidateの単位は調整した方がよさげ
ほとんどの使用例では、パス全体を再検証することをお勧めします。 より詳細な制御が必要な場合は、revalidateTag関数を使用できます。 例えば、個々のフェッチ・コールにタグを付けることができます:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
タグの管理がまた必要になるアレか、、
revalidateTagでrevalidate対象を選定できると
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts' in the cache
revalidateTag('posts')
}
データの再検証を試みてエラーがスローされた場合、最後に正常に生成されたデータがキャッシュから引き続き提供されます。 次のリクエストで、Next.jsはデータの再検証を再試行します。 エラー処理の詳細については、こちらをご覧ください。
適宜検知してre-buildしたくなりそう
fetch API を使用している場合、どのリクエストがキャッシュされているか、あるいはキャッシュされていないかを理解するために、追加のロギングを追加することができます。
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
NEXT_PRIVATE_DEBUG_CACHE=1
This will make the Next.js server console log ISR cache hits and misses. You can inspect the output to see which pages are generated during next build, as well as how pages are updated as paths are accessed on-demand.
便利
スタティック・エクスポートを作成する場合、ISRはサポートされない。
MiddlewareはオンデマンドISRリクエストに対して実行されないため、Middlewareのパスの書き換えやロジックは適用されない。 正確なパスを再検証していることを確認する。 例えば、/post-1を書き換えるのではなく、/post/1を書き換える。
ほとんどの使用例では、パス全体を再検証することをお勧めします。 より詳細な制御が必要な場合は、revalidateTag関数を使用できます。 例えば、個々のフェッチ・コールにタグを付けることができます:
revalidateとかrevalidateTagを実行した際はAPIから取得してきたデータを部分的に置き換えるとかってよりは、APIリクエストをやり直すだけっぽい。クライアントでデータを正規化してIDを見て置き換えて、、みたいな地獄の所業をやろうとしてない点は好感度高い
デフォルトでは、Next.jsはサーバーコンポーネントを使用します。 これにより、追加の設定なしで自動的にサーバーレンダリングを実装できます。必要に応じて、クライアントコンポーネントを使用することもできます。
RSCがデフォルト、CCはOptional
レンダリング作業は、個々のルートセグメントとサスペンスバウンダリに分割されます。
この単位でStreamingされてくると
サーバーレンダリングには3つのサブセットがある: スタティック、ダイナミック、ストリーミングです。
動的APIはリクエスト時にのみ知ることができる情報に依存します(プリレンダリング時には前もって知ることはできません)。 これらのAPIのいずれかを使用することで、開発者の意図が示され、リクエスト時にルート全体がダイナミックレンダリングに移行します。 これらのAPIには以下が含まれる:
cookies
headers
connection
draftMode
searchParams prop
unstable_noStore
unstable_after
これを使ってるとダイナミックルーティングになるので、ページのパフォーマンスを下げる要因となる。
利用箇所についてSuspenseで適切にラップすることで、PPRを適用する
Benefits of Client Rendering
Browser APIs: Client Components have access to browser APIs, like geolocation or localStorage.
ブラウザAPIにアクセスする必要がある場合にCCを使う
クライアント・コンポーネントを使用するには、Reactの "use client "ディレクティブをファイルの先頭、インポートの上に追加します。
'use client' を使わずにuseEffectとかブラウザAPIとかを呼び出したら自動でCCになるのか?
あ、エラー出るのか。親切〜
ただし、"use client "は、クライアントにレンダリングする必要のあるすべてのコンポーネントで定義する必要はありません。 一度境界を定義すると、そこにインポートされたすべての子コンポーネントやモジュールはクライアントバンドルに含まれるとみなされます。
PPRを使うときはRSCに切り替えたいコンポーネント層で 'use server' ディレクティブを使って切り替える?
最初のページロードを最適化するために、Next.jsはReactのAPIを使用して、クライアントコンポーネントとサーバーコンポーネントの両方について、サーバー上に静的なHTMLプレビューをレンダリングします。つまり、ユーザーがアプリケーションに最初にアクセスしたとき、クライアントがクライアント コンポーネントのJavaScriptバンドルをダウンロード、解析、実行するのを待つことなく、すぐにページのコンテンツが表示されます。
on the server
- Reactは、サーバーコンポーネントをReact Server Component Payload(RSCペイロード)と呼ばれる特別なデータ形式にレンダリングし、これにはクライアントコンポーネントへの参照が含まれます。
- Next.jsは、RSCペイロードとクライアントコンポーネントJavaScript命令を使用して、サーバー上でルートのHTMLをレンダリングします。
then, on the client- HTMLは、ルートの高速で非インタラクティブな初期プレビューを表示するために使用されます。
- React Server Components Payloadは、クライアントとサーバーのコンポーネントツリーを調整し、DOMを更新するために使用されます。
- JavaScript命令は、クライアントコンポーネントをハイドレートし、UIをインタラクティブにするために使用されます。
あわせて読みたい:https://ja.react.dev/reference/react-dom/client/hydrateRoot
その後のナビゲーションでは、クライアント・コンポーネントは、サーバーでレンダリングされたHTMLを使用せずに、完全にクライアント上でレンダリングされます。
つまり、クライアント・コンポーネントのJavaScriptバンドルがダウンロードされ、解析される。 バンドルの準備ができたら、ReactはRSCペイロードを使ってクライアントとサーバーのコンポーネントツリーを調整し、DOMを更新します。
何回も読まないと頭に入ってこねぇ、、
便利そう〜
useReducerを使う場合はCCになると。クライアント環境のステートを長時間使い回すからそうなるのか 🤔
React Contextを使ったり、propsとしてデータを渡したりする代わりに、fetchやReactのキャッシュ関数を使えば、同じデータを必要とするコンポーネントで同じデータを取得することができ、同じデータに対して重複したリクエストをする心配がない。
こっちに慣れてないとCCを乱用しちゃいそう
環境変数API_KEYは、NEXT_PUBLICが先頭に付いていないため、サーバーでのみアクセス可能なプライベート変数です。 環境変数がクライアントに漏れるのを防ぐため、Next.jsはプライベート環境変数を空文字列に置き換えます。
プレフィクスでクライアント環境でもアクセスできるかどうかを仕分けてるのか、なるほど
空文字への変換も助かる
このような意図しないクライアントによるサーバーコードの利用を防ぐために、server-only パッケージを使用することで、他の開発者が誤ってこれらのモジュールをクライアント・コンポーネントにインポートした場合に、ビルド時にエラーを表示することができます。
> npm install server-only
import 'server-only'
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
便利〜
対応するclient-onlyパッケージは、クライアント専用のコード(例えば、windowオブジェクトにアクセスするコード)を含むモジュールをマークするのに使うことができる。
こちらも便利〜
3rd party lib
現在、クライアント専用の機能を使用する npm パッケージのコンポーネントの多くは、まだこのディレクティブを持っていません。 これらのサードパーティコンポーネントは、"use client" ディレクティブを持っているので、クライアントコンポーネント内では期待通りに動作しますが、サーバーコンポーネント内では動作しません。
これ辛そう。'use client' 宣言してしまえばおkなので、そうでもないか?
この問題を解決するには、クライアント専用の機能に依存するサードパーティのコンポーネントを、独自のクライアントコンポーネントでラップします:
'use client'
import { Carousel } from 'acme-carousel'
export default Carousel
これで、サーバー コンポーネント内で <Carousel /> を直接使用できるようになりました:
これ覚えとく
context
providerをCCで定義する
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext({})
export default function ThemeProvider({
children,
}: {
children: React.ReactNode
}) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
関係ないけど、コンポーネントファイルってケバブケースで定義するのが主流なんか?
import ThemeProvider from './theme-provider'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
プロバイダがルートにレンダリングされると、アプリ全体の他のすべてのクライアント・コンポーネントがこのコンテキストを利用できるようになります。
CCの間にRSCが挟まるとどうなるのか(=どう動くのか)が気になる
ThemeProvider が <html> ドキュメント全体ではなく {children} だけをラップしていることに注目してください。 これにより、Next.js はサーバー コンポーネントの静的な部分を最適化しやすくなります。
クライアントJavaScriptバンドルのサイズを小さくするには、クライアントコンポーネントをコンポーネントツリーの下に移動することをお勧めします。
そうなりますよねー
レイアウト全体をクライアント・コンポーネントにする代わりに、インタラクティブ・ロジックをクライアント・コンポーネント(例えば<SearchBar />)に移動し、レイアウトをサーバー・コンポーネントとして維持します。 これにより、レイアウトのすべてのコンポーネントJavaScriptをクライアントに送信する必要がなくなります。
インタラクティブなコンポーネント群のまとまったstateを呼び出し元で管理したい、みたいなユースケースでは実装方針が変わる?
// SearchBar is a Client Component
import SearchBar from './searchbar'
// Logo is a Server Component
import Logo from './logo'
// Layout is a Server Component by default
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<SearchBar />
</nav>
<main>{children}</main>
</>
)
}
まぁでも上記のような検索バーとかのインタラクティブコンポーネントにstateが閉じる、もしくはアドレスみたいなReactの管理外の箇所にあるケースでは有用か。フォームみたいな要素ならstateを持たずに真面目にhtml準拠のタグを使う+useActionState、みたいなアプローチで解決するのがNext.js的には正しいのかな
サーバからクライアント・コンポーネントへの小道具の受け渡し(シリアライズ)
Server Componentでデータを取得する場合、Client Componentにpropとしてデータを渡したい場合があります。 ServerからClient Componentに渡すPropは、Reactでシリアライズ可能である必要があります。
シリアライズ、知らねーーー調べる。
doc)
シリアライズ可能な値(クライアントコードがネットワーク経由でServer Actionを呼び出す場合、渡される引数はすべてシリアライズ可能である必要があります。 Server Actionの引数でサポートされている型を以下に示します:
Primitives
string
number
bigint
boolean
undefined
null
symbol, only symbols registered in the global Symbol registry via Symbol.for
Iterables containing serializable values
String
Array
Map
Set
TypedArray and ArrayBuffer
Date
FormData instances
Plain objects: those created with object initializers, with serializable properties
Functions that are Server Actions
Promises
大体いけるな
特筆すべきは、これらがサポートされていないことだ:
React elements, or JSX
Functions, including component functions or any other function that is not a Server Action
Classes
Objects that are instances of any class (other than the built-ins mentioned) or objects with a null prototype
Symbols not registered globally, ex. Symbol('my new symbol')
なるほど、まぁ普通の使い方してたら問題にはならなさそう
サーバーとクライアント・コンポーネントのインターリーブ
インタリーブ:書込み/読込みヘッドが情報に早くアクセスできるようにコンピュータのハードディスク上で複数のデータセクタを構成すること。
なるほどわからん
この章マジでわからん
サポートされていないパターン: クライアント・コンポーネントへのサーバー・コンポーネントのインポート
えっ
サポートされているパターン: サーバーコンポーネントをプロップとしてクライアントコンポーネントに渡す
'use client'
import { useState } from 'react'
export default function ClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
)
}
<ClientComponent>は、子コンポーネントが最終的にサーバーコンポーネントの結果によって埋められることを知りません。 <ClientComponent>が負う唯一の責任は、最終的に子コンポーネントがどこに配置されるかを決定することです。
親サーバー コンポーネントでは、<ClientComponent> と <ServerComponent> の両方をインポートして、<ServerComponent> を <ClientComponent> の子として渡すことができます:
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'
// Pages in Next.js are Server Components by default
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
)
}
この方法では、<ClientComponent> と <ServerComponent> は切り離され、独立してレンダリングできます。 この場合、子コンポーネントの <ServerComponent> は、<ClientComponent> がクライアントでレンダリングされる前に、サーバーでレンダリングすることができます。
はえ〜
部分的なプリレンダリングは、カナリアでのみ利用可能な実験的な機能であり、変更される可能性があります。 本番環境で使用することはできません。
えーーー
一旦後回しにする
このページは、Next.jsがどのように動作しているかを理解するのに役立ちますが、Next.jsで生産性を上げるために必須の知識ではありません。 Next.jsのキャッシュヒューリスティックのほとんどは、APIの使用状況によって決定され、ゼロまたは最小限の設定で最高のパフォーマンスが得られるようにデフォルトが設定されています。 その代わりに例を見たい場合は、ここから始めてください。
基本的に気にせんでええですよと
デフォルトでは、Next.jsはパフォーマンス向上とコスト削減のため、可能な限りキャッシュします。 つまり、オプトアウトしない限り、ルートは静的にレンダリングされ、データリクエストはキャッシュされます。
こわ
Request Memoization
ルートがレンダリングされ、レンダリングパスが完了すると、メモリが「リセット」され、すべてのリクエストメモのエントリがクリアされる。
リクエスト単位のメモなんや
リクエストメモ化はReactの機能であり、Next.jsの機能ではありません。 他のキャッシュ機構とどのように相互作用するかを示すために、ここに含まれています。
なるほど
ルートハンドラのフェッチリクエストはReactコンポーネントツリーの一部ではないので、適用されません。
メモ化はReact Componentツリーにのみ適用される:
ルートハンドラのフェッチリクエストはReactコンポーネントツリーの一部ではないので、適用されません。
フェッチが適切でない場合(一部のデータベースクライアント、CMSクライアント、GraphQLクライアントなど)には、Reactキャッシュ関数を使用して関数をメモすることができる。
該当するケースではちまちまcacheを適用せなあかんと
キャッシュは、Reactコンポーネント・ツリーのレンダリングが終了するまで、サーバー・リクエストのライフタイムが続く。
メモ化はサーバーリクエスト間で共有されることはなく、レンダリング中にのみ適用されるので、再検証する必要はない。
レンダリングごとのキャッシュしかしない?親切っぽいが、追加のキャッシュを検討する必要がありそう
特定のAPIがクソ遅いと、リクエストごとのボトルネックとして残り続ける。
Data Cache
レンダリング中に「force-cache」オプションを指定したフェッチリクエストが最初に呼び出されると、Next.jsはキャッシュされたレスポンスがないかデータキャッシュをチェックします。
キャッシュされたレスポンスが見つかれば、即座に返され、メモ化される。
キャッシュされたレスポンスが見つからない場合は、データ・ソースにリクエストが行われ、その結果がデータ・キャッシュに保存され、メモ化される。
キャッシュされていないデータ(キャッシュ・オプションが定義されていない場合や、{ cache: 'no-store' }を使用している場合など)の場合、結果は常にデータ・ソースからフェッチされ、メモ化される。
データがキャッシュされているかキャッシュされていないかにかかわらず、Reactのレンダー・パスの間に同じデータに対して重複したリクエストを行わないように、リクエストは常にメモ化される。
4つ目はRequest Cacheの範疇っぽい。Request Cacheよりも、注視すべきはこっちかな
どちらのキャッシュメカニズムもキャッシュされたデータを再利用することでパフォーマンスを向上させるのに役立ちますが、データキャッシュはリクエストの受信やデプロイメントに渡って永続的であるのに対し、メモ化はリクエストの有効期間しか持ちません。
OK
データキャッシュは、あなたが再検証するかオプトアウトしない限り、リクエストの受信とデプロイメントに渡って永続的です。
キャッシュされたデータは2つの方法で再検証できる:
- 時間ベースの再検証: 一定時間が経過し、新たなリクエストが行われた後にデータを再検証する。 これは、変更頻度が低く、鮮度がそれほど重要でないデータに有効である。
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
- オンデマンド再検証: イベント(フォーム送信など)に基づいてデータを再検証する。 オンデマンド再検証では、タグベースまたはパスベースのアプローチを使用して、データのグループを一度に再検証できます。 これは、最新のデータをできるだけ早く表示したい場合に便利です(ヘッドレスCMSのコンテンツが更新された場合など)。
オンデマンドrevalidationを使いこなした方がよさそう
時間ベースの再検証
時間枠が過ぎても、次のリクエストはキャッシュされた(今は古い)データを返す。
Next.jsはバックグラウンドでデータの再検証をトリガーする。
SWR的な挙動だ
オンデマンドrevalidation
フェッチ・リクエストが最初に呼び出されると、データは外部データ・ソースからフェッチされ、データ・キャッシュに保存されます。
オンデマンド再検証がトリガーされると、該当するキャッシュ・エントリーはキャッシュから消去される。
次にリクエストが行われると、再びキャッシュMISSとなり、データは外部データソースからフェッチされ、データキャッシュに格納される
オンデマンドrevalidationの方が制御しやすそう
フェッチからのレスポンスをキャッシュしたくない場合は、以下のようにすることができる:
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
Full Route Cache
React Serverコンポーネントのペイロードは、クライアントサイドのルーターキャッシュ(個々のルートセグメントごとに分割された個別のメモリ内キャッシュ)に保存されます。 このルーターキャッシュは、以前に訪問したルートを保存し、将来のルートをプリフェッチすることで、ナビゲーション体験を向上させるために使用されます。
その後のナビゲーションの際やプリフェッチの際に、Next.jsはReact Server Components Payloadがルーターキャッシュに保存されているかどうかを確認します。 もしそうであれば、サーバーへの新しいリクエストの送信をスキップします。
ルートセグメントがキャッシュにない場合、Next.jsはサーバーからReact Server Components Payloadを取得し、クライアントのルーターキャッシュに入力します。
把握しておいた方がよさげ
ビルド時にルートがキャッシュされるかどうかは、静的にレンダリングされるか動的にレンダリングされるかに依存します。 静的ルートはデフォルトでキャッシュされますが、動的ルートはリクエスト時にレンダリングされ、キャッシュされません。
Dynamicパラメータ?が含まれるかどうかで決まる
Dynamicパラメータが含まれるコンポーネントをSuspenseでラップすることで、キャッシュ対象を増やすチューニングが有効?
デフォルトでは、Full Route Cache は永続的です。 これは、レンダリング出力がユーザリクエストにまたがってキャッシュされることを意味します。
dynamic = 'force-dynamic' または revalidate = 0 ルートセグメント設定オプションを使用します: これはフルルートキャッシュとデータキャッシュをスキップします。 つまり、コンポーネントはレンダリングされ、データはサーバーへのリクエストごとにフェッチされます。 クライアント側のキャッシュなので、ルーターキャッシュは適用されます。
キャッシュ回避テク
Client-side Router Cache
Next.jsには、レイアウト、ロード状態、ページごとに分割されたルートセグメントのRSCペイロードを保存するインメモリクライアントサイドルーターキャッシュがあります。
ユーザーがルート間をナビゲートすると、Next.jsは訪問したルートセグメントをキャッシュし、ユーザーがナビゲートしそうなルートをプリフェッチします。 この結果、すぐに戻る/進むナビゲーションができ、ナビゲーションの間にページをフルにリロードする必要がなく、Reactの状態とブラウザの状態が保持されます。
いいですね
レイアウトはキャッシュされ、ナビゲーションで再利用されます(部分レンダリング)。
ローディング状態はキャッシュされ、ナビゲーションの際に再利用される。
ローディング状態ってなんのこと言ってるんだろう 🤔
ページはデフォルトではキャッシュされませんが、ブラウザのバックワード・ナビゲーションやフォワード・ナビゲーションの際に再利用されます。 実験的な staleTimes 設定オプションを使うことで、ページセグメントのキャッシュを有効にできます。
ページはキャッシュされず、RSCペイロードがキャッシュされる?
このキャッシュは特にNext.jsとサーバーコンポーネントに適用され、ブラウザのbfcacheとは異なります。
bfcache、知らない
バックフォワード キャッシュ(bfcache)は、前のページと次のページにすぐに移動できるようにブラウザを最適化する機能です。これにより、特にネットワークやデバイスが低速なユーザーのブラウジング エクスペリエンスが大幅に向上します。
バックフォワード キャッシュ(bfcache)では、ユーザーが他のページに移動したときにページを破棄するのではなく、破棄を延期して JS の実行を一時停止します。ユーザーがすぐに戻ってきた場合、ページは再び表示され、JS の実行が一時停止されなくなります。
bfcache はメモリ内のページ全体のスナップショット(JavaScript ヒープを含む)
bfcacheはあとで読む
router.refreshを呼び出すと、ルーターキャッシュが無効になり、現在のルートに対してサーバーに新しいリクエストを行います。
Next.js 15では、ページセグメントはデフォルトでオプトアウトされます。
ページセグメントが何を指しているのか正しく理解できてない気がする
<Link>コンポーネントのprefetch propをfalseに設定することで、プリフェッチを無効にすることもできます。
Cache Interactions
レンダリング出力がデータに依存するため、データキャッシュの無効化またはオプトアウトを行うと、フルルートキャッシュが無効になります。
フルルートキャッシュの無効化またはオプトアウトは、データキャッシュには影響しません。 キャッシュされたデータとキャッシュされていないデータの両方を持つルートを動的にレンダリングすることができます。 これは、ページのほとんどがキャッシュされたデータを使用しているが、リクエスト時にフェッチする必要があるデータに依存しているコンポーネントがいくつかある場合に便利です。 すべてのデータを再フェッチすることによるパフォーマンスへの影響を心配することなく、動的にレンダリングすることができます。
データ キャッシュとルーター キャッシュを直ちに無効にするには、サーバー アクションで revalidatePath または revalidateTag を使用します。
Route HandlerのData Cacheを再有効化しても、Route Handlerは特定のルートに結びついていないので、Router Cacheはすぐには無効化されません。 つまり、Router Cacheはハードリフレッシュされるか、自動無効化期間が経過するまで、以前のペイロードを提供し続けます。
APIs
動的なセグメント(たとえばapp/blog/[slug]/page.js)の場合、generateStaticParamsによって提供されたパスは、ビルド時にフルルートキャッシュにキャッシュされます。 リクエスト時に、Next.jsはビルド時にわからなかったパスも、最初にアクセスされたときにキャッシュします。
ビルド時にすべてのパスを静的にレンダリングするには、generateStaticParamsにパスの完全なリストを指定します:
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
ビルド時にパスのサブセットを静的にレンダリングし、残りを実行時に初めて訪れたときにレンダリングするには、パスの部分リストを返す:
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json())
// Render the first 10 posts at build time
return posts.slice(0, 10).map((post) => ({
slug: post.slug,
}))
}
「基本的にSSGしたいが、ビルド時間が長すぎるのでアクセス数の多いページだけをとりあえずSSGしたい」みたいなケースかな
初回訪問時にすべてのパスを静的にレンダリングするには、空の配列を返す(ビルド時にパスはレンダリングされない)か、export const dynamic = 'force-static'を利用する:
export async function generateStaticParams() {
return []
}
generateStaticParams からは、空であっても配列を返す必要があります。 さもなければ、ルートは動的にレンダリングされます。
React cache function
フェッチ・リクエストは自動的にメモ化されるので、Reactキャッシュでラップする必要はない。 しかし、フェッチAPIが適していないユースケースのために、キャッシュを使ってデータリクエストを手動でメモ化することができる。 例えば、データベースクライアント、CMSクライアント、GraphQLクライアントなどです。
import { cache } from 'react'
import db from '@/lib/db'
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
cacheのアウトプットのrevalidateは関数に渡す引数の調整でやるんだったな
css module推しか?
インポートを自動的に並べ替えるリンターやフォーマッター(ESLintのsort-importなど)をオフにしてください。 CSSのインポート順序は重要なので、これは不注意にCSSに影響を与える可能性があります。
バグの原因になりうると
開発モードでは、CSSの順序が異なる場合があります。最終的なCSSの順序を確認するには、必ずビルド(next build)を確認してください。
next.config.jsのcssChunkingオプションを使って、CSSがどのようにチャンクされるかをコントロールできる。
styling/cssに遷移できない、バグっぽいので報告済み。直ったら読む
Tailwind CSSはユーティリティファーストのCSSフレームワークで、Next.jsとの相性が抜群です。
使う時に読めばよさそうだが、当ページには情報少なめ
ランタイムJavaScriptを必要とするCSS-in-JSライブラリは、現在Server Componentsではサポートされていません。CSS-in-JS を Server Components や Streaming のような新しい React の機能で使用するには、ライブラリの作者が同時レンダリングを含む最新バージョンの React をサポートする必要があります。
サーバーコンポーネントのスタイルを設定したい場合は、CSSモジュールや、PostCSSやTailwind CSSのようなCSSファイルを出力するソリューションを使用することをお勧めします。
tailwind一択かな〜
アナリティクスとモニタリング
大規模なアプリケーションのために、Next.jsは一般的なアナリティクスツールやモニタリングツールと統合し、アプリケーションのパフォーマンスを把握するのに役立ちます。 詳しくはOpenTelemetryとInstrumentationのガイドをご覧ください。
Next.js専用のダッシュボードがあるんだっけ、どこかで触ってみたい
サイズの最適化: WebPやAVIFのような最新の画像フォーマットを使用して、各デバイスに適したサイズの画像を自動的に提供します。
視覚的な安定性: 画像の読み込み時に自動的にレイアウトがずれるのを防ぎます。
ページロードの高速化: 画像は、ブラウザのネイティブレイジーローディングを使用して、ビューポートに入ったときにのみ読み込まれます。
資産の柔軟性: リモートサーバーに保存された画像も、オンデマンドでリサイズ可能
ローカル画像を使用するには、.jpg、.png、または.webp画像ファイルをインポートします。
SVG未対応
オプションとして、next.config.jsファイルでlocalPatternsを設定し、特定の画像を許可し、それ以外をブロックすることができます。
module.exports = {
images: {
localPatterns: [
{
pathname: '/assets/images/**',
search: '',
},
],
},
}
画像の公開・未公開管理もできる
リモート画像を使用するには、srcプロパティにURL文字列を指定します。
Next.jsはビルドプロセス中にリモートファイルにアクセスできないため、width、height、およびオプションのblurDataURLプロップを手動で指定する必要があります。
widthとheight属性は、画像の正しい縦横比を推測し、画像の読み込みによるレイアウトのずれを避けるために使われます。 幅と高さは、画像ファイルのレンダリングサイズを決定するものではありません。 画像サイズについて詳しくは、こちらをご覧ください。
元の画像のサイズを入力すればよさそう
画像の最適化を安全に許可するには、next.config.jsでサポートするURLパターンのリストを定義します。 悪意のある利用を防ぐため、できるだけ具体的に記述してください。 例えば、以下の設定は特定のAWS S3バケットからのイメージのみを許可します:
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 's3.amazonaws.com',
port: '',
pathname: '/my-bucket/**',
},
],
},
}
next/imageのサーバーへの過剰なアクセスの回避、的な?
先ほどの例では、ローカル画像に対して部分的なURL("/me.png")が提供されていることに注意してください。 これはローダーアーキテクチャのため可能です。
組み込みの画像ローダー以外も使えるとのこと
各ページのLCP(Largest Contentful Paint)要素となる画像に、priorityプロパティを追加する必要があります。 そうすることで、Next.jsが(preloadタグやpriority hintsなどを通じて)特別に画像を優先的に読み込むようになり、LCPが有意義に向上します。
import Image from 'next/image'
import profilePic from '../public/me.png'
export default function Page() {
return <Image src={profilePic} alt="Picture of the author" priority />
}
ユーザー体験の改善かな
LCP要素は通常、ページのビューポート内に表示される最大の画像またはテキスト・ブロックです。 次のdevを実行すると、LCP要素がpriorityプロパティを持たない<Image>の場合、コンソールの警告が表示されます。
warning投げてくれるんだ、めんどくさいけどありがたいやつだ
CLSの防止
画像のサイズがわからない場合は?
イマイチピンときてない、困った時に読み返す
Styling
styled-jsxではなく、classNameまたはstyleを使用してください。
styled-jsxは現在のコンポーネントにスコープされるため、使用できません(スタイルをグローバルとしてマークしない限り)。
backgroundに表示する例
import Image from 'next/image'
import mountains from '../public/mountains.jpg'
export default function Background() {
return (
<Image
alt="Mountains"
src={mountains}
placeholder="blur"
quality={100}
fill
sizes="100vw"
style={{
objectFit: 'cover',
}}
/>
)
}
vercel blobを使った動画の保存がおすすめされてる、ほとんどのケースではprivateなYoutube動画の埋め込みでよいのでは。。?
必要になったらまた読む
任意の Google Font を自動的にセルフホストします。 フォントはデプロイメントに含まれ、デプロイメントと同じドメインから提供されます。 ブラウザからGoogleにリクエストが送信されることはありません。
font周りの設定をそんなに頻繁にいじることがなさそう。必要になったらまた読む
アプリケーションにメタデータを追加する方法は2つあります:
設定ベースのメタデータ:静的なメタデータオブジェクトもしくは動的なgenerateMetadata関数をlayout.jsもしくはpage.jsファイルに書き出します。
ファイルベースのメタデータ:静的または動的に生成された特別なファイルをルートセグメントに追加します。
静的メタデータを定義するには、layout.jsまたはstatic page.jsファイルからMetadataオブジェクトをエクスポートします。
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '...',
description: '...',
}
export default function Page() {}
generateMetadata 関数を使用すると、動的な値を必要とするメタデータをフェッチできます。
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// read route params
const id = (await params).id
// fetch data
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// optionally access and extend (rather than replace) parent metadata
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}
generateMetadata による静的および動的メタデータは、Server Components でのみサポートされます。
fetchリクエストは、generateMetadata、generateStaticParams、Layouts、Pages、およびServer Components全体で同じデータに対して自動的にメモされます。 フェッチが利用できない場合は、Reactキャッシュを使用できます。
Next.jsは、generateMetadata内のデータ取得が完了するまで待ってから、UIをクライアントにストリーミングします。 これにより、ストリームされたレスポンスの最初の部分に<head>タグが含まれることが保証されます。
ボトルネックになりうると
ファイルベースのメタデータの方が優先順位が高く、コンフィグベースのメタデータよりも優先される。
ルートがメタデータを定義していなくても、常に追加されるデフォルトのメタタグが2つあります:
meta charsetタグは、ウェブサイトの文字エンコーディングを設定します。
meta viewportタグは、ウェブサイトのビューポート幅とスケールを設定し、さまざまなデバイス用に調整します。
全部のpage.tsxにメタデータの定義を書かないといけないのかー
app/layout.tsxの中でメタデータ生成用の関数を定義して、URLとメタデータのマッピングファイルを用意する、とかがよさそう
メタデータは、ルートセグメントから最後のpage.jsセグメントに最も近いセグメントまで、順番に評価されます。
マッピングファイルで定義しきれない generateXxx を使ったメタデータを定義したい場合は、個別のpageに定義すればよいと
Dynamic Image Generation
ImageResponseコンストラクタを使用すると、JSXとCSSを使用して動的な画像を生成できます。 これは、Open Graph画像やTwitterカードなどのソーシャルメディア画像を作成するのに便利です。 これを使用するには、next/ogからImageResponseをインポートします
import { ImageResponse } from 'next/og'
export async function GET() {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello world!
</div>
),
{
width: 1200,
height: 600,
}
)
}
デザイナーがいないとかで画像を作りづらい組織では便利?
og-imageのplayground
Edgeランタイムのみがサポートされています。 デフォルトのNode.jsランタイムは動作しません。
えっ
JSON-LD
なつかし
export default async function Page({ params }) {
const product = await getProduct(params.id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Add JSON-LD to your page */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{/* ... */}
</section>
)
}
リッチリザルトのテスター
複数のルートにサードパーティのスクリプトをロードするには、next/scriptをインポートして、レイアウトコンポーネントに直接スクリプトを含めます:
import Script from 'next/script'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<section>{children}</section>
<Script src="https://example.com/script.js" />
</>
)
}
Next.jsは、ユーザーが同じレイアウト内の複数のルート間を移動しても、スクリプトが1回しかロードされないようにします。
パフォーマンスへの不要な影響を最小限に抑えるため、特定のページやレイアウトにのみサードパーティのスクリプトを含めることをお勧めします。
Inline Scripts
<Script id="show-banner">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
<Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').classList.remove('hidden')`,
}}
/>
Next.jsがスクリプトを追跡して最適化するためには、インラインスクリプトにidプロパティを割り当てる必要があります。
Executing Additional Code
イベントハンドラをスクリプトコンポーネントで使用すると、特定のイベントが発生した後に追加のコードを実行できます:
'use client'
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
useEffectではだめなんだろうか
next/bundle-analyzerはNext.jsのプラグインで、アプリケーションのバンドルサイズを管理するのに役立ちます。
webpack-bundle-analyzer的なやつかな
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
// Client Components:
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
export default function ClientComponentExample() {
const [showMore, setShowMore] = useState(false)
return (
<div>
{/* Load immediately, but in a separate client bundle */}
<ComponentA />
{/* Load on demand, only when/if the condition is met */}
{showMore && <ComponentB />}
<button onClick={() => setShowMore(!showMore)}>Toggle</button>
{/* Load only on the client side */}
<ComponentC />
</div>
)
}
パフォチューでよく見るやつだ
サーバーコンポーネントを動的にインポートすると、サーバーコンポーネント自体ではなく、サーバーコンポーネントの子であるクライアントコンポーネントのみが遅延ロードされます。
Server ComponentでCSSなどの静的アセットを使用している場合は、プリロードされます。
SCのコードはBEで実行されるので、それはそうよねって感じ
外部ライブラリの遅延ロード
'use client'
import { useState } from 'react'
const names = ['Tim', 'Joe', 'Bel', 'Lee']
export default function Page() {
const [results, setResults] = useState()
return (
<div>
<input
type="text"
placeholder="Search"
onChange={async (e) => {
const { value } = e.currentTarget
// Dynamically load fuse.js
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
switch (metric.name) {
case 'FCP': {
// handle FCP results
}
case 'LCP': {
// handle LCP results
}
// ...
}
})
}
メトリクスを送信できる
useReportWebVitals((metric) => {
const body = JSON.stringify(metric)
const url = 'https://example.com/analytics'
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
if (navigator.sendBeacon) {
navigator.sendBeacon(url, body)
} else {
fetch(url, { body, method: 'POST', keepalive: true })
}
})
よさそう
Google Analyticsを使用している場合、id値を使用することで、メトリックの分布を手動で構築することができます(パーセンタイルなどを計算するため)。
useReportWebVitals((metric) => {
// Use `window.gtag` if you initialized Google Analytics as this example:
// https://github.com/vercel/next.js/blob/canary/examples/with-google-analytics
window.gtag('event', metric.name, {
value: Math.round(
metric.name === 'CLS' ? metric.value * 1000 : metric.value
), // values must be integers
event_label: metric.id, // id unique to current page load
non_interaction: true, // avoids affecting bounce rate.
})
})
インスツルメンテーションとは、コードを使用して監視ツールとロギングツールをアプリケーションに統合するプロセスのことです。 これにより、アプリケーションのパフォーマンスとふるまいを追跡し、運用中の問題をデバッグすることができます。
ざっくり、=監視?
この章、ほぼ意味わからんかった。多分監視周りの知識が足りてなくて読めてない
アプリの計測には OpenTelemetry を使うことをお勧めします。 OpenTelemetry は、プラットフォームにとらわれないアプリの計測方法で、コードを変更することなく、観測可能なプロバイダを変更することができます。
OpenTelemetry、なにもわからない。あとで公式Doc読む
と思ったが、公式Doc読まないとこの章読めなさそう。
オブザーバビリティデータの収集(≠可視化・監視)のためのツール、という理解。SREの人に笑われそうな理解の荒さだが
この章、 OpenTelemetryの仕様に依存した内容が多いので、やっていくぞとなったら読む、でよさそう
Next.jsでは、ルートディレクトリのpublicというフォルダの下に、画像などの静的ファイルを置くことができます。 public内のファイルは、ベースURL(/)から始まるコードから参照できます。
例えば、public/avatars/me.pngファイルは、/avatars/me.pngパスにアクセスすることで見ることができます。
Next.js cannot safely cache assets in the public folder because they may change.
ディレクトリ名はpublicでなければならない。 この名前は変更できず、静的アセットを提供するための唯一のディレクトリとなります。
Next.jsで提供されるのは、ビルド時にパブリックディレクトリにあるアセットだけです。 リクエスト時に追加されたファイルは利用できません。 永続的なファイルストレージには、Vercel Blobのようなサードパーティのサービスを使用することをお勧めします。
publicディレクトリの使い分けの基準になりそう
next/third-partiesは、Next.jsアプリケーションで一般的なサードパーティライブラリを読み込む際のパフォーマンスと開発者体験を向上させるコンポーネントとユーティリティのコレクションを提供するライブラリです。 @next/third-partiesが提供するすべてのサードパーティ統合は、パフォーマンスと使いやすさを考慮して最適化されています。
なんだこれ
next/third-partiesは現在開発中の実験的ライブラリです。 より多くのサードパーティとの統合の追加に取り組んでいる間は、最新またはカナリアフラグでインストールすることをお勧めします。
stableで使えるのか気になる
Google Third-Parties
Google Tag Manager
import { GoogleTagManager } from '@next/third-parties/google'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-XYZ" />
<body>{children}</body>
</html>
)
}
便利そう
'use client'
import { sendGTMEvent } from '@next/third-parties/google'
export function EventButton() {
return (
<div>
<button
onClick={() => sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })}
>
Send Event
</button>
</div>
)
}
めっちゃええやん、気持ち悪い独自ヘルパー関数を書かなくていいのか
サポートされているのが
- GTM
- GA
- GoogleMap埋め込み
- YouTube埋め込み
各サービスを使う時に読み返せばよさげ。
これから増えたら嬉しいな〜
next build を--experimental-debug-memory-usageで実行する。
14.2.0以降では、next build --experimental-debug-memory-usageを実行することで、Next.jsがヒープ使用量やガベージコレクションの統計情報など、ビルド中のメモリ使用量に関する情報を継続的に出力するモードでビルドを実行できます。 また、メモリ使用量が設定された上限に近づくと、ヒープスナップショットが自動的に作成されます。
デフォルトでonにしておけばよさげ
この機能は、カスタム webpack 設定をしない限り自動で有効になる Webpack ビルドワーカーオプションとは互換性がありません。
ちょっとわかんないので使う時に試しながら調べる
この章、build時とnodeランタイムのメモリの問題が発生したら読んだらよさそう
Next.jsは、next/linkを使用する際のタイプミスやその他のエラーを防ぐために、リンクを静的にタイプし、ページ間をナビゲートする際のタイプの安全性を向上させることができる。 この機能をオプトインするには、experimental.typedRoutesを有効にし、プロジェクトでTypeScriptを使用している必要がある。
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
typedRoutes: true,
},
}
export default nextConfig
import type { Route } from 'next';
import Link from 'next/link'
// No TypeScript errors if href is a valid route
<Link href="/about" />
<Link href="/blog/nextjs" />
<Link href={`/blog/${slug}`} />
<Link href={('/blog' + slug) as Route} />
// TypeScript errors if href is not a valid route
<Link href="/aboot" />
アドレスをtype-checkしてくれるのか、よさそう
フェッチ関数とページ間のデータのシリアライズがありません: サーバー上のコンポーネント、レイアウト、ページで直接フェッチできます。 このデータは、Reactで消費するためにクライアント・サイドに渡すためにシリアライズ(文字列に変換)する必要はない。 その代わり、アプリはデフォルトでServer Componentsを使用するので、余計な手順を踏むことなく、Date、Map、Setなどの値を使用できます。 以前は、Next.js固有の型でサーバーとクライアントの境界を手動で入力する必要がありました。
あれ、SCからCCにpropsを渡すときはシリアライズ可能な値に変換すること、みたいな記述があった気が?
ChatGPTに聞いたけど、SC上でデータを扱う場合にはシリアライズ可能性については意識しなくていいですよって話か。
eslintの設定を見直す時に参照すれば良さげ
Next.jsには、.env*ファイルからprocess.envに環境変数をロードするためのサポートが組み込まれています。
srcフォルダを使用している場合、Next.jsは親フォルダからのみ.envファイルを読み込み、/srcフォルダからは読み込まないことに注意してください。
Loading Environment Variables with @next/env
ORMやテストランナーのルート設定ファイルなど、Next.jsランタイムの外部で環境変数をロードする必要がある場合、@next/envパッケージを使用できます。
import { loadEnvConfig } from '@next/env'
const projectDir = process.cwd()
loadEnvConfig(projectDir)
import './envConfig.ts'
export default defineConfig({
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
})
envファイルでは他の値を参照した値を設定できる
TWITTER_USER=nextjs
TWITTER_URL=https://x.com/$TWITTER_USER -> https://x.com/nextjs
NEXT_PUBLIC_以外の環境変数はNode.js環境でのみ利用可能で、ブラウザからはアクセスできません(クライアントは別の環境で実行されます)。
よさそう
Next.jsでは、.env(すべての環境)、.env.development(開発環境)、.env.production(本番環境)にデフォルトを設定できます。
一旦全部使わない予定
Next.jsは、アプリケーション内のローカルMDXコンテンツと、サーバー上で動的に取得されるリモートMDXファイルの両方をサポートできます。 Next.jsプラグインは、マークダウンとReactコンポーネントをHTMLに変換し、サーバーコンポーネント(App Routerのデフォルト)での使用をサポートします。
コンポーネント内部でmdxが書けたらアツいぞ
next/mdxパッケージと関連パッケージは、Next.jsがマークダウンとMDXを処理できるように設定するために使用されます。 ローカルファイルからデータを取得し、拡張子.mdまたは.mdxのページを/pagesまたは/appディレクトリに直接作成できます。 Next.jsでMDXをレンダリングするには、これらのパッケージをインストールしてください:
マジ?
my-project
├── app
│ └── mdx-page
│ └── page.(mdx/md)
|── mdx-components.(tsx/js)
└── package.json
Navigating to the /mdx-page route should display your rendered MDX page.
すげー
リリースノートページとかがTS書かんでよくなるやん
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
// React component you want, including inline styles,
// components from other libraries, and more.
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
/>
),
...components,
}
}
mdxから生成されるhtmlに当てるスタイルを定義する必要があると
オプションで、MDXの内容を変換するための remark や rehype プラグインを提供することができます。 たとえば、 remark-gfm を使って GitHub Flavored Markdown をサポートすることができます。
方言を変えられる
MDXファイルやコンテンツが他の場所にある場合、サーバー上で動的に取得することができます。 これは、別のローカルフォルダ、CMS、データベースなどに保存されているコンテンツに便利です。 この用途で人気のあるコミュニティパッケージは、next-mdx-remoteです。
こういうケースもあるかー、ですよねー、
Next.jsはRustで書かれた新しいMDXコンパイラをサポートしています。 このコンパイラはまだ実験的なもので、実稼働環境での使用は推奨されていません。 新しいコンパイラを使用するには、withMDXに渡すときにnext.config.jsを設定する必要があります:
現状コンパイルが遅いのか?
src/appまたはsrc/pagesは、appまたはpagesがルート・ディレクトリに存在する場合、無視される。
ミドルウェアを使用している場合は、srcディレクトリ内に配置されていることを確認してください。
CMSと連携するときに読む
「nonce(ナンス)」とは、セキュリティや認証の文脈でよく登場する一時的な値やコードのことを指し、「一度だけ使われるべき一意の値」として利用されます。
nonceを設定したくなった時にこの章を読みながらやれば良さげ
アプリケーションの起動ポート番号を変更する場合は、http://localhost:3000 の 3000 を使用するポート番号に置き換えてください。Next.js をルート以外のディレクトリから実行している場合(たとえば Turborepo を使用している場合)、サーバー側とフルスタックのデバッグ作業に cwd を追加する必要があります。 例えば、"cwd": "${workspaceFolder}/apps/web"。
turborepo、後で調べる
実際のプロジェクトが手元にある状態で読んだ方が理解が深まりそう。あとで読む
PWA、ずっと気になりつつさわれてないので今度やってみよ
この章が簡単なtutorialになってる
favicon生成ツール
server actionとuseFormStateを使った認証の実装例は参考になる
上記の例は、教育のために認証ステップを分解しているため、冗長である。 これは、独自のセキュアなソリューションの実装がすぐに複雑になることを強調している。 プロセスを簡素化するためにAuth Libraryの使用を検討してください。
認証についてはどうせすぐ負債化するから自前で実装するなとw
認証・セッション管理・認可を自前で実装したくなったときに読み返すとよさそう
パーシャル・レンダリングのため、レイアウトでチェックを行う場合は注意が必要です。レイアウトはナビゲーションの際に再レンダリングされないため、ルート変更のたびにユーザー・セッションがチェックされません。
たしかに。user.idをkeyに渡すとかすればいいのかな
SPAでよくあるパターンは、ユーザーが認証されていない場合に、レイアウトや最上位コンポーネントでnullを返すことです。 Next.jsアプリケーションには複数のエントリーポイントがあり、ネストしたルートセグメントやサーバーアクションへのアクセスを防ぐことができないため、このパターンは推奨されません。
動的レンダリング中に、サーバー上の環境変数を安全に読み込むことができる。
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, and other Dynamic APIs
// will also opt into dynamic rendering, meaning
// this env variable is evaluated at runtime
const value = process.env.MY_VALUE
// ...
}
これ忘れてましたってのありそう。。
これにより、1つのDockerイメージを異なる値で複数の環境に昇格させることができる。
そもそもランタイムで環境変数を設定する機会がそんなに無いかも?
runtimeConfigオプションは、スタンドアロン出力モードでは動作しないため、使用は推奨しない。 代わりに、App Routerを段階的に採用することを推奨する。
Next.jsは、本当にイミュータブルなアセットには、Cache-Controlヘッダーにpublic, max-age=31536000, immutableを設定します。 これは上書きできません。 これらの不変ファイルは、ファイル名にSHA-hashが含まれているため、安全に無期限にキャッシュすることができます。 例えば、静的画像のインポート。 画像のTTLを設定できます。
デフォルトのキャッシュ期間が1年
Kubernetesのようなコンテナオーケストレーションプラットフォームを使ってNext.jsをホスティングしている場合、各Podはキャッシュのコピーを持ちます。 キャッシュはデフォルトではPod間で共有されないので、古いデータが表示されないようにするには、Next.jsキャッシュを設定してキャッシュハンドラを提供し、インメモリキャッシュを無効にします。
なるほど
Next.jsをkubernetesでホスティングするケース、、現状具体的なつらみが想像できないけど、キャッシュ周りなんやろなぁという雑感
自前のcache handlerの実装例
const cache = new Map()
module.exports = class CacheHandler {
constructor(options) {
this.options = options
}
async get(key) {
// This could be stored anywhere, like durable storage
return cache.get(key)
}
async set(key, data, ctx) {
// This could be stored anywhere, like durable storage
cache.set(key, {
value: data,
lastModified: Date.now(),
tags: ctx.tags,
})
}
async revalidateTag(tags) {
// tags is either a string or an array of strings
tags = [tags].flat()
// Iterate over all entries in the cache
for (let [key, value] of cache) {
// If the value's tags include the specified tag, delete this entry
if (value.tags.some((tag) => tags.include(tag))) {
cache.delete(key)
}
}
}
}
カスタムキャッシュハンドラを使用すると、Next.jsアプリケーションをホストするすべてのPodで一貫性を確保できます。 たとえば、RedisやAWS S3など、任意の場所にキャッシュ値を保存できます。
なるほど、Next.jsの稼働サーバーのファイルシステムにキャッシュを置いちゃうとキャッシュの差分が出るから、キャッシュだけ外部ストレージに置くのか、へー
Version Skewの問題とか、BuildIDとか、まぁ実際にやるときに読めばよさげ
Next.jsアプリルーターは、セルフホスト時にストリーミング応答をサポートします。 Nginxや同様のプロキシを使用している場合は、ストリーミングを有効にするためにバッファリングを無効にするように設定する必要があります。 たとえば、X-Accel-Bufferingをnoに設定することで、Nginxでバッファリングを無効にできます。
Nginxのバッファリングによるストリーミングレスポンスの遅延が発生する可能性があると
コード分割: サーバーコンポーネントは、ルートセグメントによる自動コード分割を可能にします。 また、必要に応じて、クライアントコンポーネントとサードパーティライブラリの遅延ロードを検討することもできます。
SPAっぽいMPAみたいな印象
動的API: CookieやsearchParamsプロップのような動的APIは、ルート全体を動的レンダリング(ルートレイアウトで使用する場合はアプリケーション全体)することに注意してください。 動的APIの利用が意図的であることを確認し、適切な場合は<Suspense>境界で囲みましょう。
コードレビューでよく見る範囲になりそう
ストリーミング: Loading UIとReact Suspenseを使用して、サーバーからクライアントにUIを徐々に送信し、データの取得中にルート全体がブロックされるのを防ぎます。
これもちゃんとやっていきたい
フォームとバリデーション: サーバーアクションを使用して、フォーム送信、サーバーサイド検証、エラー処理を行います。
クライアント側のコードでAPIを叩く構成を書いちゃいそうだけど、AppRouter環境だとそれやるメリットがない?(ChatGPTに聞いたら割とありそう)
Tainting: データオブジェクトや特定の値を汚染(taint)することで、センシティブなデータがクライアントに公開されるのを防ぐ。
これも注視したい
さらに、以下のツールは、アプリケーションに新しい依存関係を追加した場合の影響を理解するのに役立ちます:
パッケージのサイズを調べられるサイト
特定のライブラリを入れて、バンドル結果のサイズを見られるサイト
便利そう
以下で静的エクスポートが有効になる
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
output: 'export',
// Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
// trailingSlash: true,
// Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href`
// skipTrailingSlashRedirect: true,
// Optional: Change the output directory `out` -> `dist`
// distDir: 'dist',
}
module.exports = nextConfig
静的エクスポートの際は、ServerComponentに定義されたデータ取得はビルド時に実行され、バンドルに組み込まれる
まぁSSGって感じか
CCでのデータフェッチはSWRがおすすめとのこと
イメージローダーは別途定義する必要あり
ルートハンドラは、次のビルドを実行するときに静的レスポンスをレンダリングします。 GET HTTP動詞のみがサポートされています。 これは、キャッシュされたデータまたはキャッシュされていないデータから、静的なHTML、JSON、TXT、その他のファイルを生成するために使用できます。
ルートハンドラを静的エクスポートするのか、おもしろ
ただのキャッシュサーバーでは?
Nginxのような静的ホストを使用している場合、受信リクエストから正しいファイルへの書き換えを設定することができます:
server {
listen 80;
server_name acme.com;
root /var/www/out;
location / {
try_files $uri $uri.html $uri/ =404;
}
# This is necessary when `trailingSlash: false`.
# You can omit this when `trailingSlash: true`.
location /blog/ {
rewrite ^/blog/(.*)$ /blog/$1.html break;
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}
静的エクスポートの成果物はこうやって使うのか
micro frontendらしい
サービスを小さく分ける発想
micro frontendって画面内のUIエリアを区切るやつかと思ったが、違うのね
v15からの機能らしい
マルチゾーンのセットアップでは、異なるアプリケーションによって提供されるため、パスを正しいゾーンにルーティングする必要があります。 これには任意のHTTPプロキシを使用できますが、Next.jsアプリケーションの1つを使用して、ドメイン全体のリクエストをルーティングすることもできます。
いやールーティングまとめるとカオス化しそうだな。。
別のゾーンにリクエストをプロキシする設定
async rewrites() {
return [
{
source: '/blog',
destination: `${process.env.BLOG_DOMAIN}/blog`,
},
{
source: '/blog/:path+',
destination: `${process.env.BLOG_DOMAIN}/blog/:path+`,
}
];
}
異なるゾーンのパスへのリンクは、Next.jsの<Link>コンポーネントではなく、aタグを使ってください。 これは、Next.jsが<Link>コンポーネントの相対パスをプリフェッチし、ソフトナビゲートしようとするためです。
うわーめんどくせー、、
いや、他のゾーンへのリンクが頻出するわけではない、、か、、?
非同期関数やリクエストタイムデータに依存するAPIを使用すると、Next.jsは自動的にダイナミックレンダリングを選択します。 これらの処理結果を明示的にキャッシュし、アプリケーションのレンダリングパフォーマンスを最適化するには use cache ディレクティブを使用します。
ダイナミックレンダリングのパフォーマンスを上げるためのディレクティブ?
use cacheディレクティブは、unstable_cache関数を置き換えることを目的とした実験的な機能です。
なるほど、廃止にはならなさそうな雰囲気
JSONデータのキャッシュに限定され、再バリデーション期間とタグを手動で定義する必要があるunstable_cacheとは異なり、use cacheはより柔軟性があります。 データフェッチ出力やコンポーネント出力だけでなく、React Server Components (RSC)がシリアライズできるものなど、より幅広いデータをキャッシュできます。
なんでもできまっせはちょっと怖い
さらに、ユース・キャッシュは入力と出力の両方を追跡することで複雑さを自動的に管理し、誤ってキャッシュを汚染する可能性を低くします。 入力と出力の両方をシリアライズするので、キャッシュの不正な取得による問題を避けることができる。
これは何言ってるかわからん
Next.jsのuse cacheディレクティブを使うと、ルート全体、コンポーネント、関数の戻り値をキャッシュできます。 非同期関数がある場合、ファイルの先頭か関数スコープの中に use cache を追加することで、キャッシュ可能な関数としてマークできます。 これにより、Next.jsに戻り値がキャッシュされ、以降のレンダリングで再利用できることを知らせます。
// File level
'use cache'
export default async function Page() {
// ...
}
// Component level
export async function MyComponent() {
'use cache'
return <></>
}
// Function level
export async function getData() {
'use cache'
const data = await fetch('/api/data')
return data
}
静的ページについては use cache
でキャッシュしちゃう?ただ、SSGされるページってそもそもNextのサーバー上に保持されるのでは?
いや、SWR的な挙動だっけ。。?ちょっと混乱してきた
デフォルトでは、use cacheディレクティブを使用すると、Next.jsは再バリデーション期間を15分に設定します。 Next.jsはほぼ無限に近い有効期間を設定するので、頻繁な更新を必要としないコンテンツに適しています。
んー、完全に静的ってわけではない、みたいなページに使うのか。。?
統計情報ページとか?
またキャッシュレイヤーが増えるのか。。
この再バリデーション期間は、頻繁に変更されることを期待しないコンテンツには便利かもしれませんが、キャッシュの動作を設定するには、cacheLife APIとcacheTag APIを使用できます:
import { unstable_cacheLife as cacheLife } from 'next/cache'
export async function MyComponent() {
async function getData() {
'use cache'
cacheLife('days')
const data = await fetch('/api/data')
return data
}
return // Use the data here
}
cacheは他と同じくstale-while-revalidate形式で保持・提供されるとのこと
名前付きキャッシュプロファイル
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
再利用可能なキャッシュプロファイル
const nextConfig = {
experimental: {
dynamicIO: true,
cacheLife: {
biweekly: {
stale: 60 * 60 * 24 * 14, // 14 days
revalidate: 60 * 60 * 24, // 1 day
expire: 60 * 60 * 24 * 14, // 14 days
},
},
},
}
module.exports = nextConfig
cacheLife関数でのみ利用可能とのこと
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('biweekly')
// rest of code
カスタムキャッシュプロファイル
'use cache'
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife({
stale: 3600, // 1 hour
revalidate: 900, // 15 minutes
expire: 86400, // 1 day
})
// rest of code
cacheTagを使ってタグを生成し、revalidateTagによるキャッシュのパージを有効化する
import {
unstable_cacheTag as cacheTag,
unstable_cacheLife as cacheLife,
} from 'next/cache'
export async function getData() {
'use cache'
cacheLife('weeks')
cacheTag('my-data')
const data = await fetch('/api/data')
return data
}
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('my-data')
}
一番ゆるい「静的な」ページ
"use cache"
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
export default Layout({children}: {children: ReactNode}) {
return <div>{children}</div>
}
"use cache"
import { unstable_cacheLife as cacheLife } from 'next/cache'
cacheLife('minutes')
async function Users() {
const users = await fetch('/api/users');
// loop through users
}
export default Page() {
return (
<main>
<Users/>
</main>
)
}
コンポーネント・レベルでキャッシュを使用すると、そのコンポーネント内で実行されるフェッチや計算をキャッシュできます。 アプリケーション全体でコンポーネントを再利用する場合、プロップが同じ構造を維持する限り、同じキャッシュ・エントリを共有できます。
React.memoと同じ挙動?
いや、cacheLifeを使ったより細かいキャッシュの設定ができるのか。
コンポーネントレベルだとほぼ使わなそうな印象
わからんので以下を読む
最も一般的な原因はクライアント-サーバー間のウォーターフォールです。
クライアント・サーバーのRESTフェッチを、React Server Componentsを使って1往復でサーバーに移動する必要がありました。 これは、Jamstackの優れた初期読み込みパフォーマンスを犠牲にして、サーバーが時にダイナミックでなければならないことを意味します。
私たちは、このトレードオフを解決するために部分的なプリレンダリングを構築し、両方の長所を手に入れました。
しかし、その過程で、私たちが提供したキャッシュのデフォルトとコントロールのために、開発者のエクスペリエンスが損なわれた。
fetch()のデフォルトは、デフォルトでキャッシュすることでパフォーマンスを向上させるように変更されましたが、迅速なプロトタイピングや高度に動的なアプリは苦戦を強いられました。
fetch()を使用しないローカル・データベース・アクセスに対する十分な制御を提供していなかった。
unstable_cache()はありましたが、人間工学的ではありませんでした。
そのため、export const dynamic, runtime, fetchCache, dynamicParams, revalidate = ...のようなセグメントレベルのコンフィグが必要になった。
-> で、'use cache' と Suspenseを使った明示的なキャッシュ制御に至る
データ取得を伴う動的なコンポーネント
async function Component() {
return fetch(...) // no error
}
export default async function Page() {
return <Suspense fallback="..."><Component /></Suspense>
}
データ取得を伴う静的なコンポーネント
"use cache"
export default async function Page() {
return fetch(...) // no error
}
lifetimeの定義と、運用ルールの整備がめんどくさそうな印象
<Form> コンポーネントは HTML の <form> 要素を拡張し、ロード中の UI のプリフェッチ、送信時のクライアント側ナビゲーション、プログレッシブ・エンハンスメントを提供します。
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
{/* On submission, the input value will be appended to
the URL, e.g. /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
上記のフォームでは /search
に対してGETリクエストを投げてる
例えば /dashboard/invoices
ページでサーチクエリを変更するフォームにしたい場合は、
<Form action="/dashboard/invoices">
<input name="query" />
<button type="submit">Submit</button>
</Form>
みたいにすれば、Submitボタン押下でqueryクエリパラメータが付与されたURLに遷移する
検索結果ページに遷移したいフォームなどで有効そう
アクションに空の文字列 "" を渡すと、フォームは検索パラメーターを更新した同じルートに移動します。
同じユースケースなら以下でOK
<Form action="">
<input name="query" />
<button type="submit">Submit</button>
</Form>
onChangeでsubmitを発火させるやり方はどっかで見た
actionが文字列の場合、<Form>はGETメソッドを使うネイティブなHTMLフォームのように振る舞います。
Next.jsでは、フォームが表示されたときにパスをプリフェッチします。これにより、共有UI(layout.jsやloading.jsなど)がプリロードされ、ナビゲーションが高速化されます。
フォームが送信されたときに、ページ全体をリロードする代わりにクライアント側ナビゲーションを実行します。 これにより、共有UIとクライアント側の状態が保持されます。
actionが関数(Server Action)の場合、<Form>はReactフォームのように振る舞い、フォームが送信されたときにアクションを実行します。
actionに関数を渡せるようになった+いい感じに最適化されてる、って感じ
When src is SVG format, it will be blocked unless unoptimized or dangerouslyAllowSVG is enabled
dangerouslyAllowSVGがtrueならsvgを渡せる?
alt
ページの意味を変えることなく、画像を置き換えることができるテキストを含むべきである。 画像を補足するものではなく、画像の上または下のキャプションですでに提供されている情報を繰り返してはならない。
customLoaderの例
'use client'
import Image from 'next/image'
const imageLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
export default function Page() {
return (
<Image
loader={imageLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
loader のような関数を受け付けるプロップを使用するには、提供された関数をシリアライズするためにクライアントコンポーネントを使用する必要があります。
あるいは、next.config.jsのloaderFileコンフィギュレーションを使用して、アプリケーション内のnext/imageのすべてのインスタンスを、propを渡さずにコンフィギュレーションすることもできます。
やむなく一部の画像のみについて特別なローダーを付与したい場合はCCでやる必要がある
sizes
メディアクエリに似た文字列で、異なるブレイクポイントにおける画像の幅に関する情報を提供します。 sizes の値は、fillを使用している画像や、レスポンシブサイズになるようにスタイル設定されている画像のパフォーマンスに大きく影響します。
レスポンシブなUIの実装時には注意
placeholder
placeholder = 'empty' // "empty" | "blur" | "data:image/..."
blurいいなぁ
remotePatterns
悪意のあるユーザーからアプリケーションを保護するため、外部画像を使用するには設定が必要です。 これにより、Next.js Image Optimization APIから提供される外部画像は、自分のアカウントの画像だけになります。
外部画像を利用する際は要確認
Caching Behavior
- 最適化された画像は
/cache/images
に保存され、s-w-r形式で提供される
minimumCacheTTL
最適化された画像をキャッシュするTTL(Time to Live)を秒単位で設定できます。 多くの場合、ファイルの内容を自動的にハッシュ化し、Cache-Controlヘッダをimmutableにして画像を永久にキャッシュするStatic Image Importを使う方がよいでしょう。
最適化された画像の有効期限(というよりMax Age)は、minimumCacheTTLまたはアップストリームの画像Cache-Controlヘッダーのいずれか大きい方で定義されます。
module.exports = {
images: {
minimumCacheTTL: 60,
},
}
Cache-Controlヘッダをimmutableにする方法がわかってない
あ、Static Image Importを使えば自動的に設定されるのか
アプリケーションで利用する画像についてはプロジェクト内に画像を保存して、Static Image Importを利用すれば良さげ
ただ、Imageのパフォーマンスが求められるのってSEOに影響する記事ページとかな気がしている
その場合CMSに登録された画像を利用するケースが多い気がしていて、となるとminimumCacheTTLを眺めに設定するのがよいのかな
ただし
現時点ではキャッシュを無効にするメカニズムはないので、minimumCacheTTLを低く保つのが最善です。 そうでなければ、手動でsrc propを変更するか、<distDir>/cache/imagesを削除する必要があるかもしれません。
この説明が気になっている。
キャッシュが残って困るケースってあるのか。。?デプロイ時に毎回コンテナを作り直す設定にしていれば気にならない気がするのだけど。
dangerouslyAllowSVG
これはSVGをnext/imageで提供するとなったら改めて読む
Responsive Images
レスポンシブ画像の例
import Image from 'next/image'
import me from '../photos/me.jpg'
export default function Author() {
return (
<Image
src={me}
alt="Picture of the author"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
/>
)
}
元画像が動的またはリモートのURLの場合、レスポンシブ画像の正しい縦横比を設定するために、幅と高さも指定する必要があります:
import Image from 'next/image'
export default function Page({ photoUrl }) {
return (
<Image
src={photoUrl}
alt="Picture of the author"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
width={500}
height={300}
/>
)
}
props: href
import Link from 'next/link'
// Navigate to /about?name=test
export default function Page() {
return (
<Link
href={{
pathname: '/about',
query: { name: 'test' },
}}
>
About
</Link>
)
}
props: prefetch
プリフェッチはプロダクションでのみ有効です。
null(default): プリフェッチの動作は、ルートが静的か動的かによって異なります。 静的ルートの場合、ルート全体がプリフェッチされます (すべてのデータを含みます)。 動的なルートの場合、loading.js の境界を持つ最も近いセグメントまでの部分的なルートがプリフェッチされます。
true: スタティックルートでもダイナミックルートでも、フルルートがプリフェッチされる。
ダイナミックルートについてフルルートをプリフェッチするとどうなるんだ。。?
examples
ナビゲーションで特定のIDにスクロールしたい場合は、URLに#ハッシュリンクを付加するか、href propにハッシュリンクを渡します。 これは、<Link>が<a>要素にレンダリングされるので可能です。
<Link href="/dashboard#settings">Settings</Link>
// Output
<a href="/dashboard#settings">Settings</a>
Linkの子コンポーネントがaタグである場合はpassHref属性を付与する
import Link from 'next/link'
import styled from 'styled-components'
// This creates a custom component that wraps an <a> tag
const RedLink = styled.a`
color: red;
`
function NavLink({ href, name }) {
return (
<Link href={href} passHref legacyBehavior>
<RedLink>{name}</RedLink>
</Link>
)
}
export default NavLink
Linkコンポーネントの子要素がaタグである場合、aタグが複数連なるため、legacyBehavior
属性を付与してaタグの生成を止める
アロー関数をラップする場合はforwardRefを使ってrefを受け渡す
import Link from 'next/link'
import React from 'react'
// Define the props type for MyButton
interface MyButtonProps {
onClick?: React.MouseEventHandler<HTMLAnchorElement>
href?: string
}
// Use React.ForwardRefRenderFunction to properly type the forwarded ref
const MyButton: React.ForwardRefRenderFunction<
HTMLAnchorElement,
MyButtonProps
> = ({ onClick, href }, ref) => {
return (
<a href={href} onClick={onClick} ref={ref}>
Click Me
</a>
)
}
// Use React.forwardRef to wrap the component
const ForwardedMyButton = React.forwardRef(MyButton)
export default function Page() {
return (
<Link href="/about" passHref legacyBehavior>
<ForwardedMyButton />
</Link>
)
}
prefetchするURLを出し分けたい場合、middlewareで制御する
import { NextResponse } from 'next/server'
export function middleware(request: Request) {
const nextUrl = request.nextUrl
if (nextUrl.pathname === '/dashboard') {
if (request.cookies.authToken) {
return NextResponse.rewrite(new URL('/auth/dashboard', request.url))
} else {
return NextResponse.rewrite(new URL('/public/dashboard', request.url))
}
}
}
'use client'
import Link from 'next/link'
import useIsAuthed from './hooks/useIsAuthed' // Your auth hook
export default function Page() {
const isAuthed = useIsAuthed()
const path = isAuthed ? '/auth/dashboard' : '/public/dashboard'
return (
<Link as="/dashboard" href href={path}>
Dashboard
</Link>
)
}
onLoad, onReady, onErrorは全てRSC未対応
Parallel Routes利用時のスロットおよびルートセグメント全体のフォールバック用ファイル
not-found.js
notFound()
が呼ばれたときのフォールバック
OTel関連
このファイルは、新しいNext.jsサーバーインスタンスが開始されたときに一度だけ呼び出されるregister関数をエクスポートします。
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel('next-app')
}
サーバー起動時に読み込まれるという特性を利用して外部のmiddlewareを設定したり、
以下のようにsentryを組み込んだりできる
import { captureRequestError } from '@sentry/nextjs'
export const onRequestError = captureRequestError;
<title>や<meta>などの<head>タグをルート・レイアウトに手動で追加すべきではありません。 代わりに、<head>要素のストリーミングや重複除去などの高度な要件を自動的に処理するMetadata APIを使うべきです。
複数のルートレイアウトにまたがってナビゲートすると、(クライアントサイドナビゲーションとは対照的に)フルページロードが発生します。 例えば、app/(shop)/layout.jsを使用している/cartから、app/(marketing)/layout.jsを使用している/blogに移動すると、全ページがロードされます。
キャッシュが使えないからそらそうやろ感がある
mdx-components.js|tsxファイルは、@next/mdxをApp Routerで使用するために必要で、これがないと動作しません。 また、スタイルのカスタマイズにも使用できます。
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
}
}
ミドルウェアはEdgeランタイムのみをサポートしています。 Node.jsランタイムは使用できません。
予想されるnotFound()エラーをキャッチするだけでなく、ルートのapp/not-found.jsファイルは、アプリケーション全体の一致しないURLも処理します。 つまり、あなたのアプリが扱っていないURLにアクセスしたユーザーには、app/not-found.jsファイルによってエクスポートされたUIが表示されます。
バージョン14以前では、paramsはsynchronousなpropでした。 後方互換性を保つために、Next.js 15でも同期的にアクセスできますが、将来的にはこの動作は廃止される予定です。
searchParams props
現在のURLの検索パラメータを含むオブジェクトに解決するプロミス。
クエリパラメータって検索用とは限らなくない?と思ったけど、”クエリ”パラメータだから正しいのか。むしろ検索用でないパラメータを渡しているのが脱法っぽい
ログアウトAPIとか?
import { cookies } from 'next/headers'
export async function GET(request: NextRequest) {
const cookieStore = await cookies()
const a = cookieStore.get('a')
const b = cookieStore.set('b', '1')
const c = cookieStore.delete('c')
}
dynamicIOフラグがオンの場合は無効となり、将来的には非推奨となる。
ページ単位でconfigを設定する、的なやつか
export const experimental_ppr = true
// true | false
export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
'use cache' とcacheLife?の組み合わせを使った明示的なキャッシュ設定が必須になるなら、確かに消えそう
revalidate
1つのルートの各レイアウトとページで最も低い再検証頻度が、ルート全体の再検証頻度を決定します。 これは子ページが親レイアウトと同じ頻度で再検証されることを保証します。
マジックナンバーじゃなくて定数で管理した方がよさそう。そのために cacheLife
にデフォルトのセット文字列があるのかも?
Generate icons using code
リテラル画像ファイルを使用するだけでなく、コードを使ってプログラムでアイコンを生成することもできます。
この機能いる。。。?
PWAで使う機能らしい
robots.txtをいい感じに定義・生成できるらしい。
機能が薄そうなので、ロックインを防ぐならベタ書きでもいいかも?
こちらも同じく、sitemap.xmlをいい感じに定義・生成できると。
生成用のバッチとかを作りたくないので、個人的には好み
tagのマスタはどこかでオブジェクトとして定義して管理したい
Dynamic APIを利用していないが、ページをキャッシュしたくない場合に利用するとのこと
import { connection } from 'next/server'
export default async function Page() {
await connection()
// Everything below will be excluded from prerendering
const rand = Math.random()
return <span>{rand}</span>
}
バージョン14以前では、cookiesは同期関数でした。 後方互換性を保つため、Next.js 15でも同期的にアクセスできますが、この動作は将来廃止される予定です。
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return '...'
}
'use server'
import { cookies } from 'next/headers'
export async function delete(data) {
(await cookies()).delete('name')
}
draftModeの話。
バージョン14以前では、draftModeは同期関数でした。 後方互換性を保つため、Next.js 15でも同期的にアクセスできますが、この動作は将来廃止される予定です。
v14までは同期関数だったがv15で非同期関数になったものが多いなー
draft modeの切り替えをcookieの有無で判定していて、cookieをセットするためのAPIを置くのがおすすめらしい
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
const draft = await draftMode()
draft().enable()
return new Response('Draft mode is enabled')
}
draftモード有効化するためにAPI叩くの怠いので、特定のAPIを叩くボタンみたいなのを置く&環境でボタンをだし分ける、とかが丸い気がする
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: { id: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// read route params
const id = (await params).id
// fetch data
const product = await fetch(`https://.../${id}`).then((res) => res.json())
// optionally access and extend (rather than replace) parent metadata
const previousImages = (await parent).openGraph?.images || []
return {
title: product.title,
openGraph: {
images: ['/some-specific-page-image.jpg', ...previousImages],
},
}
}
export default function Page({ params, searchParams }: Props) {}
metadataを生成する関数。parentとかが便利そう
メタデータがランタイム情報に依存しない場合は、generateMetadataではなくstatic metadataオブジェクトを使用して定義する必要があります。
searchParams は page.js セグメントでのみ使用できます。
page.js以外でmetadata定義しなさそうなので大丈夫そうとは思いつつ。
title
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
template: '...',
default: '...',
absolute: '...',
},
}
templateとabsolute、便利そうだけど、別に使わなくてよくねという印象
metadataBase
metadataの設定時に用いるURLのプレフィクス
export const metadata = {
metadataBase: new URL('https://acme.com'),
alternates: {
canonical: '/',
languages: {
'en-US': '/en-US',
'de-DE': '/de-DE',
},
},
openGraph: {
images: '/og-image.png',
},
}
<link rel="canonical" href="https://acme.com" />
<link rel="alternate" hreflang="en-US" href="https://acme.com/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://acme.com/de-DE" />
<meta property="og:image" content="https://acme.com/og-image.png" />
metadataBaseのdefaultValue
本番環境ではVERCEL_PROJECT_PRODUCTION_URLが使用され、プレビュー環境ではVERCEL_BRANCH_URLが優先され、存在しない場合はVERCEL_URLにフォールバックされます。
sitemapを動的に生成
DynamicParamsがネストしたルートでは、親の generateStaticParams
の実行後に 子の generateStaticParams
を実行することで、別個にページをビルドできる
// Generate segments for [category]
export async function generateStaticParams() {
const products = await fetch('https://.../products').then((res) => res.json())
return products.map((product) => ({
category: product.category.slug,
}))
}
export default function Layout({ params }: { params: { category: string } }) {
// ...
}
// Generate segments for [product] using the `params` passed from
// the parent segment's `generateStaticParams` function
export async function generateStaticParams({
params: { category },
}: {
params: { category: string }
}) {
const products = await fetch(
`https://.../products?category=${category}`
).then((res) => res.json())
return products.map((product) => ({
product: product.id,
}))
}
export default function Page({
params,
}: {
params: { category: string; product: string }
}) {
// ...
}
revalidatePath は、含まれるパスが次に訪問されたときにのみキャッシュを無効にします。 つまり、動的なルートセグメントで revalidatePath を呼び出しても、一度に多くの再検証が行われることはありません。 無効化は、パスが次に訪問されたときにのみ起こる。
LayoutPathの再検証
import { revalidatePath } from 'next/cache'
revalidatePath('/blog/[slug]', 'layout')
// or with route groups
revalidatePath('/(main)/post/[slug]', 'layout')
これは、次のページ訪問時に、提供されたページ・ファイルにマッチするすべてのURLを再検証します。 特定のページの下のページは無効になりません。 例えば、/blog/[slug]は/blog/[slug]/[author]を無効にしません。
unstable_afterを使うと、レスポンス(もしくはプリレンダー)の終了後に実行される作業をスケジュールできます。 これは、ロギングや分析など、レスポンスをブロックしてはならないタスクやその他の副作用のために便利です。
ページが閉じられた場合どうなるか気になる、まぁ処理されないだろうな
unstable_afterは、レスポンスが正常に完了しなかった場合でも実行される。 エラーがスローされた場合や、notFound や redirect がコールされた場合も含みます。
外部サービスにweb vitalをプールしたい場合に使えそうだが、vercel謹製の計測ツールがあるとのこと
ビルド中のフェーズを取得できる
// @ts-check
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants')
module.exports = (phase, { defaultConfig }) => {
if (phase === PHASE_DEVELOPMENT_SERVER) {
return {
/* development only config options here */
}
}
return {
/* config options for all phases except development here */
}
}
利用可能なフェーズ
export const PHASE_EXPORT = 'phase-export'
export const PHASE_PRODUCTION_BUILD = 'phase-production-build'
export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
export const PHASE_TEST = 'phase-test'
Vercelにデプロイすると、Next.jsプロジェクトのグローバルCDNが自動的に設定されます。 手動でAsset Prefixを設定する必要はありません。
この方法で指定された環境変数は、常にJavaScriptバンドルに含まれます。環境変数名の前にNEXT_PUBLIC_を付けるのは、環境ファイルや.envファイルを通して指定する場合にのみ効果があります。
うわ、地雷臭
rust製のmdxコンパイラが使える。ビルドが早くなるかも?
HMRより開発体験がよいらしい
あとで読む
pagesRouterのドキュメントも読んでおく
pages/index.js → /
pages/blog/index.js → /blog
あーこんなんだったなそういえば
複数のレイアウトが必要な場合は、ページにgetLayoutプロパティを追加して、レイアウト用のReactコンポーネントを返すことができます。 これにより、ページごとにレイアウトを定義することができます。 関数を返すので、必要であれば複雑なネストしたレイアウトも可能です。
こんなんあったんだ。appRouterではlayout.ts or template.ts で代替される機能