Nuxt3のSSRでサーバーからクライアントにどうやって値を渡しているのか
はじめに
Nuxt3のrcが公開されたので使ってみました。
作ったのはこちら。
入力したテキストをいい感じにぶつ切りにしてGIFにしてくれるツールです。
まぁ、作った内容は置いておきます。
Nuxt3を使ってみた感想としては全体的に2よりも使いやすくなった。
いろいろ書き方があった2と違って絞られているので書きやすいですね。
ただところどころ気になった点があったのでそれを書きたいのですが、その前に自分で調べたことをまとめたいと思います。
といったところでタイトルにある内容の本編にいきます。
本編
NuxtJSやNext.jsはSSR(サーバーサイドレンダリング)ができます。
この際、js内で状態を管理するストアの値をサーバーからクライアントに渡す工程があります。
2度同じ計算したり同じ通信したりすることになったりサーバーからクライアントに渡したHTMLとストアの値が異なることになる恐れがあるからですね。
では、Nuxt3ではその仕組みがどうなっているのかを見ていきます。
Nuxt3では状態を管理するuseState
というメソッドが提供されています。
これを使って状態管理をしていけばSSRの際にもサーバーからクライアントに値を渡してくれるようになっている便利なメソッドです。
ちなみにuseState
での状態管理はページを跨いでも維持されます。
const counter = useState('counter',() => 0)
使い方として、上のように第1引数にキーを設定して第2引数に初期化関数を指定する形で使います。
まずはこの中身がどうなっているのかをみていきます。
useState
の中身は上のようになっています。
useNuxtApp
でnuxt
というオブジェクトを持ってきてnuxt.payload.state
内の指定されたキーをtoRef
で取得します。
値がある場合はそのままrefオブジェクトを返します。
値がundefined
で初期化関数が指定されている場合は、初期化関数を実行して初期化します。
そして、nuxt.payload.state[key]
にいれた上で、refオブジェクトを返します。
nuxt.payload.state
でKey-Value型の形で管理しているわけですね。
次にnuxt.payload.state
について見ていきます。
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__
を探してみたところ、サーバーでのレンダリング部分にありました。
async function renderHTML (payload: any, rendered: RenderResult, ssrContext: NuxtSSRContext) {
const state = `<script>window.__NUXT__=${devalue(payload)}</script>`
...省略...
}
サーバーでのHTML生成の際に、scriptタグ内にwindow.__NUXT__
が書き出されるんですね。
devalue
はJSON.stringify
のようなものみたいです。
ただ、いろいろ便利な感じ。
気になる方はこちらをどうぞ。
まとめ
いろいろ見ていった結果、SSRの際の値の受け渡し方法がわかりました。
-
useState
を使って状態管理をすることでnuxt.payload.state
に状態がまとまめられる - サーバーでHTMLをレンダリングする際にscriptタグで
window.__NUXT__
として状態が書き出される - クライアントで
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さんで校正の方をしたりました。
もともとプログラマー向けではないのでコードブロックを表示する機能がないのですごく読みにくいですがもしよければこの記事を書いた過程も見ていただければと。
Discussion