Open9

Next.js 12 x React 18 について調べたメモ

TakepepeTakepepe

'RSC'.reverse() == 'CSR'

現状公開されている情報から、Edge Functions を起点に Component を Streaming することが Vercel x Next.js x React 18 のゴールに見える。Edge Functions で RSC(React Server Components) を SSR するメリットは以下の様に考えている。

  • 物理的に近い Edge サーバーなので速い
  • V8 Isolete 実行環境のため立ち上がりの速い(Node 依存の Serverless より速い)
  • CSR と比較しラウンドトリップが少ない

このPR で Next.js に Server Components を導入した Shu Ding 氏がピン留めしている以下ツイートは必読。

https://twitter.com/shuding_/status/1453160745714208769

TakepepeTakepepe

Vercel Edge Fucntions の裏側は Cloudflare Workers

Hacker News で言及されているとの情報があった。確証は持てていないが、その前提で構成を見ると腑に落ちる点が多い。

https://news.ycombinator.com/item?id=29003514

また、example でも '@tsndr/cloudflare-worker-jwt のパッケージを利用している場面が見られるので、可能性としては高そう。

https://github.com/vercel/examples/blob/main/edge-functions/jwt-authentication/lib/auth.ts#L4

koichikkoichik

確証は持てていないが

HNで言及しているアカウント leerob さんはVercelの中の人でNextConfのキーノートやエッジ関連のセッションに登壇していたLee Robinson氏なのでVercle Edge Functionの中身はCloudflare Workersで確定でいいと思います

https://nextjs.org/conf/speakers/leerob

ちなみにHNの該当スレにはVercel CEOのGuillermo Rauch氏も参加してますね

TakepepeTakepepe

_middleware は Edge Runtime 導入の先駆け

今回導入された _middlware によって、Static Generation + Edge Computing という選択肢が増えた。RFC が出て来た時はアプリケーションサーバー middleware かと思ったが誤認で、これは Edge Functions の middleware 相当になる。そのため session を利用したコードなどは期待できず、必要であれば従来通りの対応となる。

Edge Functions で実行することを想定しているため、利用できる API が制限されている。Unsupported APIs を確認すると、V8 Isolete という環境(Cloudflare Workers)を意識したコードにしなければいけない理由がわかる。

https://nextjs.org/docs/api-reference/edge-runtime

Edge Functions middleware となると、オリジンサーバー単体(開発サーバー含め)でも動作を再現する必要がある。その再現環境としての sandbox が Next 12 に同包されており、以下が実装詳細。「Vercel にデプロイせずとも _middleware が使える」という仕組みの根幹になっている。sandbox のおかげで Vercel 以外でも動きはするが、Vercel 程のパフォーマンスメリットは期待できない。

https://github.com/vercel/next.js/tree/canary/packages/next/server/web/sandbox

オリジンサーバーで V8 Isolete 環境を再現するため、sandbox 内部では Node VM モジュールが利用されている。

https://nodejs.org/api/vm.html

同じ原理で「Vercel にデプロイせずとも React Server Components が使える」と理解していて、このタイミングで _middleware が発表されたのは合点がいく。ReadableStream の polyfill が実装されている点も、RSC@Edge の下準備に見える。

TakepepeTakepepe

Edge Functions で SSR するメリット

以下のツイートがわかりやすい。bot 向けには レンダリングを待ち、そうでない場合はクライアントに Stream を返す。

  • Steam API による SEO 観点の懸念がなくなる
  • bot 以外は Stream API が返るため、レンダリングブロックが発生しない

https://twitter.com/RyanCarniato/status/1454175307120869377

Cloudflare Workers では Cache-Control header をセットできるため、このメリットなど含め Next.js の Data fetching API が今後進化するのではないかと推測している。

koichikkoichik

bot向けの切り替えはエッジのみならずオリジンでもできることなので、それだけではエッジでSSRするメリットとは言いがたいように思います

React 18 WGでもストリーミングSSRとSEO (bot) との関連は言及されていてReactのAPIとしても対応可能です (レンダリングとしてはストリーミングだけど全てバッファリングして最後にまとめてフラッシュするようにできる)
https://github.com/reactwg/react-18/discussions/37#discussioncomment-842581

上記へのレスでSebastian Markbåge氏はbotであってもストリーミングSSRするメリット (高速なサイトと判定される) があり、ストリーミングするしないはトレードオフだと言及していることも要注意

TakepepeTakepepe

Streaming getServerSideProps

https://www.youtube.com/watch?app=desktop&v=Nl4OwNhh2QI

Next Conf でもセッションがあった こちらの RFCStreaming getServerSideProps は上記とは別物。これまでの getServerSideProps を拡張し、レンダリングブロックを減らす仕組みが検討されている。以下のとおり非同期関数を返却する props が書ける様になる。

async function getServerSideProps() {
  const user = await getUser();
  if (!user) return { notFound: true };
  return {
     props: async () => {
       const data = await getData(user);
       return data;
     }
  }
}

この非同期関数は export default 関数描画前に実行される。Promise 解決前は null が描画され、Promise 解決後は flush と同時にデータがある状態で Component が描画される。この機能が入ることで getStaticPropsfallback: true を設定している状況と近しいページを作ることが出来る。

export default function App({ component, pageProps })
    return (
     <>
      <Head>
        <link rel="preload" ... />
      </Head>
      {pageProps ? <Component ...pageProps /> : null}
     </>
    )
}

【余談】この RFC が実装されれば前節 "Edge Functions で SSR するメリット" で述べた内容同様に、bot 向けにはデータ取得を待ち、閲覧者向けには即時 View を返すという実装が可能になる。

koichikkoichik

このクラシックストリーミングはbot対策が不要な気がします (推測)
React 18のストリーミングSSRはJSを駆使して後から届く断片をDOMに接ぎ木しまくるのでJSを解釈できないbotには不適切だったり、JSを解釈できるGoogleのbotであってもキューイングされてしまうのでインデクシングに時間がかかるといったデメリットがあります
しかしクラシックストリーミングは (おそらく) レンダリングの途中で </head> までを早めにフラッシュするだけなのでbotには静的なページに見えるんじゃないでしょうか
推測ですがこう変わるだけではないかと

現在のSSR

(ブロッキング) <html><head>...</head><body>...</body></html>

クラシックSSR

<html><head>...</head> (ブロッキング) <body>...</body></html>