🌍Dynamic IOの成り立ちと"use cache"の深層2024/12/13に公開8件Next.jsswctechDiscussionsumiren2024/12/31記事ありがとうございます! ======= Dynamic IOではさらにこれを発展させ、Dynamic RenderingにStatic Renderingを入れ子にすることが可能となります。 Dynamic IOではレスポンスの開始がデータフェッチによってブロックされることがないため、効率的な配信が可能となります。 ここだけちょっとわからなかったので、質問してもよろしいでしょうか。 https://nextjs.org/blog/our-journey-with-caching これを見る限り、dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもので、あまりレンダリングの考え方には影響ないのかなと、少なくとも自分は理解しました。 なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。Static ComponentをIOキャッシュを伴うものと見ているかどうかが結構大きい気がしています。 また、Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。 ======= 特に記事を直してほしいとかではなく単純な疑問です!返事を求めるのも本来厚かましいことと承知のうえですので、気が向いたらの回答で構いません!記事が興味深かったので質問でした! akfm_sato2024/12/31質問ありがとうございます! 大晦日にご苦労様ですww dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもの こちらについては「Our Journey with Caching」にて But for a moment, I'd like you to forget about all that. (でも、しばらくの間、そんなことは忘れてほしい。) とあるように、Dynamic IOの世界観では今までの世界観は一旦忘れて良いかと思います。 その上でDynamic IOのコンセプトを簡単に書き出すと「動的I/O処理を含む場合には遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ということになります。 これらの前提のもと、 Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。 こちらに回答すると、フェッチなどの動的I/O処理を扱うには「遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ので、ページのレンダリングでフェッチを即座に扱うことができなくなりました。つまり新たにできることが増えたというより、できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 一方、 なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。 こちらは新たにできるようになったことになります。従来はPPRによって「静的コンポーネントに動的コンポーネントを埋め込む」ことはできたのですが、「動的コンポーネントに静的コンポーネントを埋め込む」ことはできませんでした。これはキャッシュのオプトインが"use cache"ディレクティブによってするような設計になったことで、新たにできるようになったものです。 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 async function Cart() { const session = await getSession(); // `session.cart.products`に商品ID配列があると仮定 // session情報を参照してるので`<Cart>`は動的にレンダリングする必要がある return ( <> {session.cart.products.map(product => ( {/* `<ProductCard/>`は`"use cache"`でキャッシュされるコンポーネントでもOK */} {/* `"use cache"`指定時のキャッシュはRSC Payloadで永続化されるのでコンポーネントもキャッシュ可能 */} <ProductCard id={product.id}> ))} </> ) } 以上になります。 知りたきことに対する回答になってますかね...? sumirenさんが認識してる「現状でできていること」と僕が認識してる「現状でできていること」に齟齬があって変な回答になってないか少し心配です。 ↑の回答で意味がわからないという部分があれば、この「現状でできていること」も併せてご教示いただけると幸いです。 sumiren2024/12/31ありがとうございます!! できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 あぁ、たしかに!あまりやることがないので忘れてましたが、たしかにいままでは同期でfetchを扱うことで非Streamingでのレンダリングも可能でしたね!それがStreaming or Staticの二択になったのは確かに1つのレンダリング上の変化ですね。 Dynamic IOではレスポンスの開始がデータフェッチによってブロックされることがないため、効率的な配信が可能となります。 この記載は、上記の制約を指しているであってますかね! 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 なるほど。これって今までできてなかったでしたっけ...?動くけどPPRはできなかったということですかね?自分の現状に対する理解は以下です! 動的コンポーネントでfetch等を含まない静的コンポーネントを利用することはできる 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) ただしPPRはされず、動的コンポーネントのレンダリング時にHTMLなりRSC Payloadに変換される akfm_sato2024/12/31補足ありがとうございます! 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) 多分ここに細かいニュアンスの齟齬がありそうな気がします👀 従来はFull Route Cache=Route全てをキャッシュするか、Data Cache=fetch単位でキャッシュするかしかなかったんですが、Dynamic IOでは"use cache"をコンポーネント単位で付与できるようになりました。これは従来できなかったことだと思います。 僕の実装例で言うと、<Cart>内の<ProductCard>の内容がキャッシュ可能だったとしても、従来は↓のようにfetchにキャッシュを指定する必要がありました。 async function ProductCard({ id }: { id: string }) { console.log("render <ProductCard>"); const product = await fetch(`.../${id}`, { cache: "force-cache", }) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されました。 これがDynamic IO以降は以下になります。 async function ProductCard({ id }: { id: string }) { "use cache"; console.log("render <ProductCard>"); const product = await fetch(`.../${id}`) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 ここが従来との違いになります! いかがでしょうか...? sumiren2025/01/01に更新ありがとうございます!長くやりとり継続いただいていてすみません!できる範囲で回答お願いします! この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 なるほど、ようやく理解できました!言い方に違いはあるかもしれませんが、以下のように理解しております。 現状でもfetch等に関していえばData Cacheで個別にコントロールできるし、Dynamic IOでも変わらない Dynamic IOではFull Route CacheやData Cacheの使い分けがなくなり、内部的にDynamic IOのキャッシュで統一される 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) コンポーネントや関数レベルでキャッシュが可能になることで、たとえば動的なコンポーネントが内部的に静的なコンポーネント(use cacheや非同期関数のない全くの静的なコンポーネントや、非同期関数を含んでいてもuse cacheされたもの)をrenderしていても、静的な部分はデータレベルではなくコンポーネントレベルでキャッシュでき、レンダリングのオーバーヘッドが削減される ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね?(自分の直感が誤っているかもなので、検証してみようかなと思います)(もはやDynamic IOにベットするのだから現状の挙動は気にしなくて良い感もありますが) export default function Page() { return ( <> ... {/* Static Rendering /} <Suspense fallback={<Loading />}> {/ Dynamic Rendering /} <DynamicComponent> {/ Static Rendering */} <StaticComponent /> </DynamicComponent> </Suspense> </> ); } akfm_sato2025/01/01 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される はい、そうなります! そして本記事で扱ってるように、"use cache"によるキャッシュの永続化は従来とは異なるものになってます。 (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) こちらもおっしゃる通り...だと思います! Our Journey with CachingでRoute Segment ConfigはEscape Hatchesと表現されており、今後は併用されない世界観を作っていくつもりなのであろうと予想しています。 最もこれは僕がそう読み解いてるだけで、直接明言されているわけではないので「おそらくそうだろう」という予想のお話ですが... ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね? はい、Dynamic IO以前の世界(PPR有効時)では<StaticComponents>は動的にレンダリングされて、回避する手段はなかった認識です。 Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います! koichik2025/01/01experimental.dynamicIOを有効にしているのにRoute Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず 全てのRoute Segment Configではないかもだけど少なくともexport const dynamic = ...はそうだった記憶です sumiren2025/01/02ありがとうございます! Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います なるほど、たしかにレンダリング時にPromiseをthrowする仕組みを考えると、コンポジションなどのコードベース上での依存関係というよりは、実際に解決されるツリーでSuspense内のものは動的になりそうな気がしますね!腑に落ちました! ありがとうございました! Route Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず こちら、ちょうど試しててexport runtime = edgeとかでも怒られてました!ありがとうございます! 返信を追加
sumiren2024/12/31記事ありがとうございます! ======= Dynamic IOではさらにこれを発展させ、Dynamic RenderingにStatic Renderingを入れ子にすることが可能となります。 Dynamic IOではレスポンスの開始がデータフェッチによってブロックされることがないため、効率的な配信が可能となります。 ここだけちょっとわからなかったので、質問してもよろしいでしょうか。 https://nextjs.org/blog/our-journey-with-caching これを見る限り、dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもので、あまりレンダリングの考え方には影響ないのかなと、少なくとも自分は理解しました。 なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。Static ComponentをIOキャッシュを伴うものと見ているかどうかが結構大きい気がしています。 また、Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。 ======= 特に記事を直してほしいとかではなく単純な疑問です!返事を求めるのも本来厚かましいことと承知のうえですので、気が向いたらの回答で構いません!記事が興味深かったので質問でした! akfm_sato2024/12/31質問ありがとうございます! 大晦日にご苦労様ですww dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもの こちらについては「Our Journey with Caching」にて But for a moment, I'd like you to forget about all that. (でも、しばらくの間、そんなことは忘れてほしい。) とあるように、Dynamic IOの世界観では今までの世界観は一旦忘れて良いかと思います。 その上でDynamic IOのコンセプトを簡単に書き出すと「動的I/O処理を含む場合には遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ということになります。 これらの前提のもと、 Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。 こちらに回答すると、フェッチなどの動的I/O処理を扱うには「遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ので、ページのレンダリングでフェッチを即座に扱うことができなくなりました。つまり新たにできることが増えたというより、できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 一方、 なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。 こちらは新たにできるようになったことになります。従来はPPRによって「静的コンポーネントに動的コンポーネントを埋め込む」ことはできたのですが、「動的コンポーネントに静的コンポーネントを埋め込む」ことはできませんでした。これはキャッシュのオプトインが"use cache"ディレクティブによってするような設計になったことで、新たにできるようになったものです。 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 async function Cart() { const session = await getSession(); // `session.cart.products`に商品ID配列があると仮定 // session情報を参照してるので`<Cart>`は動的にレンダリングする必要がある return ( <> {session.cart.products.map(product => ( {/* `<ProductCard/>`は`"use cache"`でキャッシュされるコンポーネントでもOK */} {/* `"use cache"`指定時のキャッシュはRSC Payloadで永続化されるのでコンポーネントもキャッシュ可能 */} <ProductCard id={product.id}> ))} </> ) } 以上になります。 知りたきことに対する回答になってますかね...? sumirenさんが認識してる「現状でできていること」と僕が認識してる「現状でできていること」に齟齬があって変な回答になってないか少し心配です。 ↑の回答で意味がわからないという部分があれば、この「現状でできていること」も併せてご教示いただけると幸いです。 sumiren2024/12/31ありがとうございます!! できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 あぁ、たしかに!あまりやることがないので忘れてましたが、たしかにいままでは同期でfetchを扱うことで非Streamingでのレンダリングも可能でしたね!それがStreaming or Staticの二択になったのは確かに1つのレンダリング上の変化ですね。 Dynamic IOではレスポンスの開始がデータフェッチによってブロックされることがないため、効率的な配信が可能となります。 この記載は、上記の制約を指しているであってますかね! 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 なるほど。これって今までできてなかったでしたっけ...?動くけどPPRはできなかったということですかね?自分の現状に対する理解は以下です! 動的コンポーネントでfetch等を含まない静的コンポーネントを利用することはできる 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) ただしPPRはされず、動的コンポーネントのレンダリング時にHTMLなりRSC Payloadに変換される akfm_sato2024/12/31補足ありがとうございます! 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) 多分ここに細かいニュアンスの齟齬がありそうな気がします👀 従来はFull Route Cache=Route全てをキャッシュするか、Data Cache=fetch単位でキャッシュするかしかなかったんですが、Dynamic IOでは"use cache"をコンポーネント単位で付与できるようになりました。これは従来できなかったことだと思います。 僕の実装例で言うと、<Cart>内の<ProductCard>の内容がキャッシュ可能だったとしても、従来は↓のようにfetchにキャッシュを指定する必要がありました。 async function ProductCard({ id }: { id: string }) { console.log("render <ProductCard>"); const product = await fetch(`.../${id}`, { cache: "force-cache", }) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されました。 これがDynamic IO以降は以下になります。 async function ProductCard({ id }: { id: string }) { "use cache"; console.log("render <ProductCard>"); const product = await fetch(`.../${id}`) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 ここが従来との違いになります! いかがでしょうか...? sumiren2025/01/01に更新ありがとうございます!長くやりとり継続いただいていてすみません!できる範囲で回答お願いします! この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 なるほど、ようやく理解できました!言い方に違いはあるかもしれませんが、以下のように理解しております。 現状でもfetch等に関していえばData Cacheで個別にコントロールできるし、Dynamic IOでも変わらない Dynamic IOではFull Route CacheやData Cacheの使い分けがなくなり、内部的にDynamic IOのキャッシュで統一される 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) コンポーネントや関数レベルでキャッシュが可能になることで、たとえば動的なコンポーネントが内部的に静的なコンポーネント(use cacheや非同期関数のない全くの静的なコンポーネントや、非同期関数を含んでいてもuse cacheされたもの)をrenderしていても、静的な部分はデータレベルではなくコンポーネントレベルでキャッシュでき、レンダリングのオーバーヘッドが削減される ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね?(自分の直感が誤っているかもなので、検証してみようかなと思います)(もはやDynamic IOにベットするのだから現状の挙動は気にしなくて良い感もありますが) export default function Page() { return ( <> ... {/* Static Rendering /} <Suspense fallback={<Loading />}> {/ Dynamic Rendering /} <DynamicComponent> {/ Static Rendering */} <StaticComponent /> </DynamicComponent> </Suspense> </> ); } akfm_sato2025/01/01 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される はい、そうなります! そして本記事で扱ってるように、"use cache"によるキャッシュの永続化は従来とは異なるものになってます。 (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) こちらもおっしゃる通り...だと思います! Our Journey with CachingでRoute Segment ConfigはEscape Hatchesと表現されており、今後は併用されない世界観を作っていくつもりなのであろうと予想しています。 最もこれは僕がそう読み解いてるだけで、直接明言されているわけではないので「おそらくそうだろう」という予想のお話ですが... ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね? はい、Dynamic IO以前の世界(PPR有効時)では<StaticComponents>は動的にレンダリングされて、回避する手段はなかった認識です。 Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います! koichik2025/01/01experimental.dynamicIOを有効にしているのにRoute Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず 全てのRoute Segment Configではないかもだけど少なくともexport const dynamic = ...はそうだった記憶です sumiren2025/01/02ありがとうございます! Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います なるほど、たしかにレンダリング時にPromiseをthrowする仕組みを考えると、コンポジションなどのコードベース上での依存関係というよりは、実際に解決されるツリーでSuspense内のものは動的になりそうな気がしますね!腑に落ちました! ありがとうございました! Route Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず こちら、ちょうど試しててexport runtime = edgeとかでも怒られてました!ありがとうございます! 返信を追加
akfm_sato2024/12/31質問ありがとうございます! 大晦日にご苦労様ですww dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもの こちらについては「Our Journey with Caching」にて But for a moment, I'd like you to forget about all that. (でも、しばらくの間、そんなことは忘れてほしい。) とあるように、Dynamic IOの世界観では今までの世界観は一旦忘れて良いかと思います。 その上でDynamic IOのコンセプトを簡単に書き出すと「動的I/O処理を含む場合には遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ということになります。 これらの前提のもと、 Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。 こちらに回答すると、フェッチなどの動的I/O処理を扱うには「遅延させる(<Suspense>)かキャッシュする("use cache")必要がある」ので、ページのレンダリングでフェッチを即座に扱うことができなくなりました。つまり新たにできることが増えたというより、できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 一方、 なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。 こちらは新たにできるようになったことになります。従来はPPRによって「静的コンポーネントに動的コンポーネントを埋め込む」ことはできたのですが、「動的コンポーネントに静的コンポーネントを埋め込む」ことはできませんでした。これはキャッシュのオプトインが"use cache"ディレクティブによってするような設計になったことで、新たにできるようになったものです。 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 async function Cart() { const session = await getSession(); // `session.cart.products`に商品ID配列があると仮定 // session情報を参照してるので`<Cart>`は動的にレンダリングする必要がある return ( <> {session.cart.products.map(product => ( {/* `<ProductCard/>`は`"use cache"`でキャッシュされるコンポーネントでもOK */} {/* `"use cache"`指定時のキャッシュはRSC Payloadで永続化されるのでコンポーネントもキャッシュ可能 */} <ProductCard id={product.id}> ))} </> ) } 以上になります。 知りたきことに対する回答になってますかね...? sumirenさんが認識してる「現状でできていること」と僕が認識してる「現状でできていること」に齟齬があって変な回答になってないか少し心配です。 ↑の回答で意味がわからないという部分があれば、この「現状でできていること」も併せてご教示いただけると幸いです。
sumiren2024/12/31ありがとうございます!! できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。 あぁ、たしかに!あまりやることがないので忘れてましたが、たしかにいままでは同期でfetchを扱うことで非Streamingでのレンダリングも可能でしたね!それがStreaming or Staticの二択になったのは確かに1つのレンダリング上の変化ですね。 Dynamic IOではレスポンスの開始がデータフェッチによってブロックされることがないため、効率的な配信が可能となります。 この記載は、上記の制約を指しているであってますかね! 以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。 なるほど。これって今までできてなかったでしたっけ...?動くけどPPRはできなかったということですかね?自分の現状に対する理解は以下です! 動的コンポーネントでfetch等を含まない静的コンポーネントを利用することはできる 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) ただしPPRはされず、動的コンポーネントのレンダリング時にHTMLなりRSC Payloadに変換される
akfm_sato2024/12/31補足ありがとうございます! 同様に動的コンポーネントでfetch等(ビルド時 or revalidate)を含む静的コンポーネントを利用することはできる(例えばProductCardが3分ごとにデータrevalidateしてたとしても問題なく動く) 多分ここに細かいニュアンスの齟齬がありそうな気がします👀 従来はFull Route Cache=Route全てをキャッシュするか、Data Cache=fetch単位でキャッシュするかしかなかったんですが、Dynamic IOでは"use cache"をコンポーネント単位で付与できるようになりました。これは従来できなかったことだと思います。 僕の実装例で言うと、<Cart>内の<ProductCard>の内容がキャッシュ可能だったとしても、従来は↓のようにfetchにキャッシュを指定する必要がありました。 async function ProductCard({ id }: { id: string }) { console.log("render <ProductCard>"); const product = await fetch(`.../${id}`, { cache: "force-cache", }) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されました。 これがDynamic IO以降は以下になります。 async function ProductCard({ id }: { id: string }) { "use cache"; console.log("render <ProductCard>"); const product = await fetch(`.../${id}`) // ... } この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 ここが従来との違いになります! いかがでしょうか...?
sumiren2025/01/01に更新ありがとうございます!長くやりとり継続いただいていてすみません!できる範囲で回答お願いします! この場合"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。 この<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。 なるほど、ようやく理解できました!言い方に違いはあるかもしれませんが、以下のように理解しております。 現状でもfetch等に関していえばData Cacheで個別にコントロールできるし、Dynamic IOでも変わらない Dynamic IOではFull Route CacheやData Cacheの使い分けがなくなり、内部的にDynamic IOのキャッシュで統一される 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) コンポーネントや関数レベルでキャッシュが可能になることで、たとえば動的なコンポーネントが内部的に静的なコンポーネント(use cacheや非同期関数のない全くの静的なコンポーネントや、非同期関数を含んでいてもuse cacheされたもの)をrenderしていても、静的な部分はデータレベルではなくコンポーネントレベルでキャッシュでき、レンダリングのオーバーヘッドが削減される ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね?(自分の直感が誤っているかもなので、検証してみようかなと思います)(もはやDynamic IOにベットするのだから現状の挙動は気にしなくて良い感もありますが) export default function Page() { return ( <> ... {/* Static Rendering /} <Suspense fallback={<Loading />}> {/ Dynamic Rendering /} <DynamicComponent> {/ Static Rendering */} <StaticComponent /> </DynamicComponent> </Suspense> </> ); }
akfm_sato2025/01/01 現状でもpprを指定すればFull Route Cacheをユーザー側があまり意識しないのは同じだが、内部的に、Full Route CacheはSuspense境界によって制御してData Cacheはfetchやunstable_cacheで制御していたのが、use cacheの仕組みによるコンポーネントや関数レベルでのキャッシュに統一される はい、そうなります! そして本記事で扱ってるように、"use cache"によるキャッシュの永続化は従来とは異なるものになってます。 (だとするとRoute Segment Configみたいな考え方はなくなっていって、そこの挙動は全部pprになっていくんですかね...?) こちらもおっしゃる通り...だと思います! Our Journey with CachingでRoute Segment ConfigはEscape Hatchesと表現されており、今後は併用されない世界観を作っていくつもりなのであろうと予想しています。 最もこれは僕がそう読み解いてるだけで、直接明言されているわけではないので「おそらくそうだろう」という予想のお話ですが... ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね? はい、Dynamic IO以前の世界(PPR有効時)では<StaticComponents>は動的にレンダリングされて、回避する手段はなかった認識です。 Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います!
koichik2025/01/01experimental.dynamicIOを有効にしているのにRoute Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず 全てのRoute Segment Configではないかもだけど少なくともexport const dynamic = ...はそうだった記憶です
sumiren2025/01/02ありがとうございます! Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います なるほど、たしかにレンダリング時にPromiseをthrowする仕組みを考えると、コンポジションなどのコードベース上での依存関係というよりは、実際に解決されるツリーでSuspense内のものは動的になりそうな気がしますね!腑に落ちました! ありがとうございました! Route Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず こちら、ちょうど試しててexport runtime = edgeとかでも怒られてました!ありがとうございます!
Discussion
記事ありがとうございます!
=======
ここだけちょっとわからなかったので、質問してもよろしいでしょうか。
これを見る限り、dynamic IOのスコープはfetchのオプション + unstable_cacheを代替するもので、あまりレンダリングの考え方には影響ないのかなと、少なくとも自分は理解しました。
なので、DynamicComponentのなかにStaticComponentを入れ子にしたときに具体的にどのように挙動が変わるのか理解できませんでした。Static ComponentをIOキャッシュを伴うものと見ているかどうかが結構大きい気がしています。
また、Dynamic IOではフェッチによってブロックされることがないというのも、現状でできていることとの違いがわからなかったので、よければ詳しく教えてもらえないでしょうか。
=======
特に記事を直してほしいとかではなく単純な疑問です!返事を求めるのも本来厚かましいことと承知のうえですので、気が向いたらの回答で構いません!記事が興味深かったので質問でした!
質問ありがとうございます!
大晦日にご苦労様ですww
こちらについては「Our Journey with Caching」にて
とあるように、Dynamic IOの世界観では今までの世界観は一旦忘れて良いかと思います。
その上でDynamic IOのコンセプトを簡単に書き出すと「動的I/O処理を含む場合には遅延させる(
<Suspense>)かキャッシュする("use cache")必要がある」ということになります。これらの前提のもと、
こちらに回答すると、フェッチなどの動的I/O処理を扱うには「遅延させる(
<Suspense>)かキャッシュする("use cache")必要がある」ので、ページのレンダリングでフェッチを即座に扱うことができなくなりました。つまり新たにできることが増えたというより、できることを減らしてパフォーマンスを追求する形です。ちょっと語弊があるのですが少々極端にいうと、PPRを強制するような世界観のイメージです。一方、
こちらは新たにできるようになったことになります。従来はPPRによって「静的コンポーネントに動的コンポーネントを埋め込む」ことはできたのですが、「動的コンポーネントに静的コンポーネントを埋め込む」ことはできませんでした。これはキャッシュのオプトインが
"use cache"ディレクティブによってするような設計になったことで、新たにできるようになったものです。以下に例を挙げてみます。ユーザー情報を含み、動的にレンダリングする必要のある
<Cart>内で、<ProductCard>のようなキャッシュ可能なコンポーネントを埋め込むことなどができるようになったというイメージです。以上になります。
知りたきことに対する回答になってますかね...?
sumirenさんが認識してる「現状でできていること」と僕が認識してる「現状でできていること」に齟齬があって変な回答になってないか少し心配です。
↑の回答で意味がわからないという部分があれば、この「現状でできていること」も併せてご教示いただけると幸いです。
ありがとうございます!!
あぁ、たしかに!あまりやることがないので忘れてましたが、たしかにいままでは同期でfetchを扱うことで非Streamingでのレンダリングも可能でしたね!それがStreaming or Staticの二択になったのは確かに1つのレンダリング上の変化ですね。
この記載は、上記の制約を指しているであってますかね!
なるほど。これって今までできてなかったでしたっけ...?動くけどPPRはできなかったということですかね?自分の現状に対する理解は以下です!
補足ありがとうございます!
多分ここに細かいニュアンスの齟齬がありそうな気がします👀
従来はFull Route Cache=Route全てをキャッシュするか、Data Cache=fetch単位でキャッシュするかしかなかったんですが、Dynamic IOでは
"use cache"をコンポーネント単位で付与できるようになりました。これは従来できなかったことだと思います。僕の実装例で言うと、
<Cart>内の<ProductCard>の内容がキャッシュ可能だったとしても、従来は↓のようにfetchにキャッシュを指定する必要がありました。この場合
"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されました。これがDynamic IO以降は以下になります。
この場合
"render <ProductCard>"は<Cart>がレンダリングされるたびに出力されません。この
<ProductCard>がrevalidateされるまでずっとコンポーネントレベルでキャッシュされます。ここが従来との違いになります!
いかがでしょうか...?
ありがとうございます!長くやりとり継続いただいていてすみません!できる範囲で回答お願いします!
なるほど、ようやく理解できました!言い方に違いはあるかもしれませんが、以下のように理解しております。
ちなみに記事の下記の例はコンポジションになっていますが、コンポジションでも現状は実行時のツリー上にSuspenseの下にいたら動的にrenderされる認識でいらっしゃいますかね?(自分の直感が誤っているかもなので、検証してみようかなと思います)(もはやDynamic IOにベットするのだから現状の挙動は気にしなくて良い感もありますが)
export default function Page() {
return (
<>
...
{/* Static Rendering /}
<Suspense fallback={<Loading />}>
{/ Dynamic Rendering /}
<DynamicComponent>
{/ Static Rendering */}
<StaticComponent />
</DynamicComponent>
</Suspense>
</>
);
}
はい、そうなります!
そして本記事で扱ってるように、
"use cache"によるキャッシュの永続化は従来とは異なるものになってます。こちらもおっしゃる通り...だと思います!
Our Journey with CachingでRoute Segment ConfigはEscape Hatchesと表現されており、今後は併用されない世界観を作っていくつもりなのであろうと予想しています。
最もこれは僕がそう読み解いてるだけで、直接明言されているわけではないので「おそらくそうだろう」という予想のお話ですが...
はい、Dynamic IO以前の世界(PPR有効時)では
<StaticComponents>は動的にレンダリングされて、回避する手段はなかった認識です。Dynamic APIs利用時にNext.jsはpostponeという特別なPromiseをthrowします。これはbuild時には永遠に解決されないPromiseです。このPromiseがthrowされたら
<Suspense>境界に対して動的レンダリングをマークしていくので、コンポジションにしてても<Suspense>境界単位が同一な<StaticComponents>は動的にレンダリングされるはずだと思います!experimental.dynamicIOを有効にしているのにRoute Segment Configを指定すると数ヶ月前には既にビルドでエラーになったはず全てのRoute Segment Configではないかもだけど少なくとも
export const dynamic = ...はそうだった記憶ですありがとうございます!
なるほど、たしかにレンダリング時にPromiseをthrowする仕組みを考えると、コンポジションなどのコードベース上での依存関係というよりは、実際に解決されるツリーでSuspense内のものは動的になりそうな気がしますね!腑に落ちました!
ありがとうございました!
こちら、ちょうど試しててexport runtime = edgeとかでも怒られてました!ありがとうございます!