Server Side Renderingしてみました
2022/02/14 13:30:00 +00:00
やりたいこと
現在運用しているサーバがsinatra + hamlで構成されており、バージョンアップに追従できておらず、普段からrubyを触らなくなってしまったのもあり、機能追加も億劫になっていました。そこでどうせなら
- Node.jsでサーバサイドを書いてみて
- テンプレートエンジンは使い慣れているReactを使う
をやってみよう、の2点です。
サーバ
最初は Express で書き始めたんですが、State of JavaScript 2021/2022 で見かけた Fastify で書き直しました。単純に知らないライブラリを触っておこう、それにパフォーマンスもよいらしいし、くらいのモチベーションです。(最終的にはコンテナに載せるのでfastifyが謳う「ロガーに何々を選びました」という惹句は決め手になりにくいなとは思いました)(が、最初からログがJSON形式なのは今っぽさを感じます)
触ってみて分かったfastifyのよいところのひとつに、テストが書きやすさがあります。Fastify - Testing にある通り、inject
メソッドで擬似的にHTTPリクエストを送ることができ、簡単にテストが書けます。
SSR
単純にSSRしたいだけなら ReactDOMServer – React にあるメソッド群を使えばよいだけでした。renderToStaticMarkup
は、渡したコンポーネントのレンダリング結果をHTML文字列として返してくれるメソッドです。
ただ今回返したいレスポンスは、HTMLではなくXMLでした。JSXの型として扱えるのは、@types/reactに定義されてある通りで、HTML要素に存在するものに限られています。当然、xml名前空間で指定されたDTDのことなんてTypeScriptは知らないので、以下のコードをXMLを扱うコンポーネントと同じモジュールに貼り付けて回避しました。
declare global {
namespace JSX {
interface IntrinsicElements {
[elemName: string]: unknown;
}
}
}
これでもなお、pubDate
のようなキャメルケースの要素を扱うと、レンダリング時にreact-domが次のようなwarningを出力します。
Warning: <pubDate /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.
これを抑止するには NODE_ENV=production
を指定して実行すればよさそうです。productionではないテスト実行時などにこのwarningが出力されて鬱陶しいですが、大きな問題にはならないので一旦目をつむることにします。
hydration
せっかくSSRにしたので、特にユースケースはないけどhydrationも試してみました。単語自体はSSRの文脈で目にするけれども、実際に何が起きているか、どう実装するかというのは全く知らなかったので触れてみるか、というモチベーションです。
具体的には、ReactDOMのhydrateメソッド を使います。クライアント用JSファイルのエントリポイントにて、いつもの ReactDOM.render(<App />, document.getElementById('root'));
ではなく、ReactDOM.hydrate(<App />, document.getElementById('root'));
とします 。renderと全く同じインターフェイスなので分かりやすいですね。hydrateメソッドによって、クライアント側では改めてDOM構築をせずに、イベントハンドラなどのアタッチ処理が行われるようです。
ここでAppコンポーネントが何かしらのデータを受け取るインターフェイスになっていたら、クライアント側ではどこからデータを取得すればよいかが悩みポイントでした。例えば何らかのAPIレスポンスをサーバ側では自由に取得できたとして、クライアント側で同様に取得できるか分からないし、できたとしてサーバ・クライアントで二重に取得するのは無駄で無意味です。そこでNext.jsではどうやってるのか調べてみると、SSRしたHTMLの中にJSONデータをシリアライズしたものを埋め込んでいるようでした。つまりクライアント側でそれを取得してJSON.parseできればよいわけですね。愚直な感じはしますが、workはします。
ここまできたらcss in jsも試してみたいと思って試行錯誤したんですが、SSRするのにビルドが必要そうだったりして簡単にできるのはここまでということにして、ひとまず終わりにします。
まとめ
- 普段Next.jsなどで気にせず行われているSSRやhydrateは、身近なAPIでできる(その先がムズい)
- Next.js触りたての頃、windowオブジェクトを評価してビルドがコケることがちょいちょいあったけど、そうなる理由がよくわかりました