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が描画されるってことか。
ますます使いたくねーの気持ちが増してゆく
パラレルルート使う必要あるか?
うーん、読んだけどモチベーションがイマイチわからんかった。また後日読み返したら納得度が上がるかも?
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なので一旦無視する
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を書き換える。