🐾

Nuxt3のSSRでサーバーからクライアントにどうやって値を渡しているのか

2022/06/19に公開

はじめに

Nuxt3のrcが公開されたので使ってみました。

作ったのはこちら。

https://txtgif.txtlive.net

入力したテキストをいい感じにぶつ切りにしてGIFにしてくれるツールです。
まぁ、作った内容は置いておきます。

Nuxt3を使ってみた感想としては全体的に2よりも使いやすくなった。
いろいろ書き方があった2と違って絞られているので書きやすいですね。

ただところどころ気になった点があったのでそれを書きたいのですが、その前に自分で調べたことをまとめたいと思います。

といったところでタイトルにある内容の本編にいきます。

本編

NuxtJSやNext.jsはSSR(サーバーサイドレンダリング)ができます。
この際、js内で状態を管理するストアの値をサーバーからクライアントに渡す工程があります。
2度同じ計算したり同じ通信したりすることになったりサーバーからクライアントに渡したHTMLとストアの値が異なることになる恐れがあるからですね。

では、Nuxt3ではその仕組みがどうなっているのかを見ていきます。

Nuxt3では状態を管理するuseStateというメソッドが提供されています。

https://v3.nuxtjs.org/guide/features/state-management

これを使って状態管理をしていけばSSRの際にもサーバーからクライアントに値を渡してくれるようになっている便利なメソッドです。
ちなみにuseStateでの状態管理はページを跨いでも維持されます。

const counter = useState('counter',() => 0)

使い方として、上のように第1引数にキーを設定して第2引数に初期化関数を指定する形で使います。

まずはこの中身がどうなっているのかをみていきます。

https://github.com/nuxt/framework/blob/b82c6e3e962ca52ae6556251655642e0e344b456/packages/nuxt/src/app/composables/state.ts#L11-L24

useStateの中身は上のようになっています。
useNuxtAppnuxtというオブジェクトを持ってきてnuxt.payload.state内の指定されたキーをtoRefで取得します。
値がある場合はそのままrefオブジェクトを返します。
値がundefinedで初期化関数が指定されている場合は、初期化関数を実行して初期化します。
そして、nuxt.payload.state[key]にいれた上で、refオブジェクトを返します。

nuxt.payload.stateKey-Value型の形で管理しているわけですね。

次にnuxt.payload.stateについて見ていきます。

https://github.com/nuxt/framework/blob/d7d97cd7d15c508016c8089a4983ec71b1059375/packages/nuxt/src/app/nuxt.ts

nuxtのインターフェースが見つかったので内容を確認してみます。

interface _NuxtApp {
  ...省略...
  payload: {
    serverRendered?: boolean
    data?: Record<string, any>
    state?: Record<string, any>
    rendered?: Function
    [key: string]: any
  }
  ..省略...
}

型がRecord<string,any>になっています。

もうちょっと見てみます。

export function createNuxtApp (options: CreateOptions) {
  const nuxtApp: NuxtApp = {
    ...省略...
    payload: reactive({
      data: {},
      state: {},
      _errors: {},
      ...(process.client ? window.__NUXT__ : { serverRendered: true })
    }),
    ...省略...
  } as any as NuxtApp
  ...省略...
}

実際にnuxtのオブジェクトがが作られてるcreateNuxtAppメソッドを見つけました。
中身を見てみましょう。

ふむふむ。
payloadはreactiveメソッドから返されるリアクティブなオブジェクトみたいです。
だから先ほどのuseState内でtoRefが使われていたわけですね。
なるほど。

useStateでの状態管理はリアクティブなオブジェクトであるnuxt.payload.stateでまとめて管理されているのがわかりました。

管理しているところはわかったので、それをどうやってサーバーからクライアントに渡しているのかを探してみます。

nuxtが作られているのがcreateNuxtAppメソッドなのでこの中で何かしらしているはず。
していないとこれ以降の処理でサーバーからの値を使いづらくない?ということでもうちょっとcreateNuxtAppを見てみます。

...(process.client ? window.__NUXT__ : { serverRendered: true })

createNuxtApp内のここが鍵になりそうです。
process.clientでクライアントの場合とサーバーの場合で条件わけしています。

クライアントの場合、window.__NUXT__payloadに展開しているようです。

window.__NUXT__について探ってみることにします。

window.__NUXT__を探してみたところ、サーバーでのレンダリング部分にありました。

https://github.com/nuxt/framework/blob/822928b07e41b4579d7079927988cd6cd90c48ce/packages/nuxt/src/core/runtime/nitro/renderer.ts

async function renderHTML (payload: any, rendered: RenderResult, ssrContext: NuxtSSRContext) {
  const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
  ...省略...
}

サーバーでのHTML生成の際に、scriptタグ内にwindow.__NUXT__が書き出されるんですね。

devalueJSON.stringifyのようなものみたいです。
ただ、いろいろ便利な感じ。
気になる方はこちらをどうぞ。
https://github.com/nuxt-contrib/devalue

まとめ

いろいろ見ていった結果、SSRの際の値の受け渡し方法がわかりました。

  1. useStateを使って状態管理をすることでnuxt.payload.stateに状態がまとまめられる
  2. サーバーでHTMLをレンダリングする際にscriptタグでwindow.__NUXT__として状態が書き出される
  3. クライアントでnuxtのオブジェクトを作る際にそれをあわせる

このような流れでサーバーからクライアントへ値が受け渡されていることがわかりました。

ちなみに

Nuxt3では、Http(s)通信をキャッシュするuseFetchというメソッドがあります。
これも状態管理と同様にSSRの際にサーバーからクライアントにキャッシュを渡します。
中身としては、nuxt.payload.dataにキャッシュ情報をまとめていて状態管理と同様にデータをクライアントに渡す形になります。

console.log(window.__NUXT__)とブラウザのデバッグコンソールで実行すると受け渡されているデータを簡単にみれますね。

気になった点

1つ目

SSRでの状態の受け渡しの仕組みをまとめました。

その上で気になった点を書きたいと思います。

SSRをする方針だと、基本的には状態管理はuseStateを使うことになるのかなと考えています。
サーバー側で計算したのにクライアントで再度計算するのはどうなの?と思うからです。

調べた通りuseStateでの状態はnuxt.payload.stateにまとめられていきます。

nuxtというオブジェクトは単一のものです。

異なるページでuseStateが使われていた場合、同じく値がnuxt.payload.stateに入ります。

気になる点はここ。
いろいろ状態管理が入り続けていくとブラウザのメモリーは大丈夫?ってことです。

useStateで管理される状態なんですけど、現状削除するメソッドが見当たりませんでした。

大丈夫なのかなーと。

ブラウザのメモリ管理がどうなっているか詳しく知らないですし、そもそもuseStateを基本的に使うっていう設計が間違っている気もしますがどうなのでしょう。

ちなみにuseFetchでHttp(s)通信をキャッシュした場合も同じ問題にならないかなとも思っています。
useFetchはキャッシュを削除して再度取得するというreflesh機能がありますが、useStateと同じく削除するメソッドが見当たらなかったりします。
サーバーで通信してクライアントでも同じ通信をするのはやりたくないことだと思うのでuseFetchも使うと思いますがこちらもnuxt.payload.dataにたまっていくので大丈夫なのかな?と気になっています。

2つ目

composablesディレクトリにuse〇〇とかって作るじゃないですか。
composaleって状態管理まわりが関わるときに作ると思っているのですが正しいのでしょうか。
正しいとして、状態管理が関わらない場合の処理とかってどこにまとめるものでなんでしょう。
そもそもあるのか?という話ではありますが。

さいごに

Nuxt3でのSSR時のサーバーからクライアントへの状態管理の値の受け渡しの仕方について調べてまとめました。
Nuxt3を触る際の一助になればいいかなと思います。

そして、気になった点を少々書きました。
どなたか気になることに回答いただけると嬉しいです。

また機会があれば、Nuxt3について気になることや調べたことを書きたいと思います。

欄外

ちなみにですが、執筆過程を記録・配信・公開するサービス テキストライブを運営しております。

この記事もテキストライブで初稿を書いてZennさんで校正の方をしたりました。

もともとプログラマー向けではないのでコードブロックを表示する機能がないのですごく読みにくいですがもしよければこの記事を書いた過程も見ていただければと。

https://txtlive.net/lr/1655629917904

Discussion