たまには Vue や React から離れて Svelte を触ってみようと始めたのも束の間。 Emotion で作っていた UI ライブラリを動かそうにも、CSR時ではうまく行くものの、SSR時に一瞬スタイルが適用されないちらつきが発生しました。

これの原因は明らかで、サーバサイドで動作した際、ブラウザ上であれば Emotion が自動的に挿入していたスタイルが、SSR の結果に反映されていないから、です。
React などの場合、レンダリングした結果を取得して @emotion/server などの関数でうまくやってやれば良いんだけれど、全くと言っていいほど資料がなくて一晩苦労したので備忘を記録します。

結論

+layout.svelte

import { browser } from '$app/environment';

const getStyles = () => {
  return browser
    ? ''
  : `<style>` +
    Object.keys(cache.registered)
    .map((key) => {
    return `.${key} {${cache.registered[key]}}`;
  })
    .join('\n') +
    '</style>';
};

(HTMLの最後に)
<svelte:head>
  {@html getStyles()}
</svelte:head>

これは何

SSR でスタイルを適用させるということは、とにかく初期レスポンスに返す HTML にスタイルが入っていれば良いはず。
Reactなどでは、renderした結果から情報を抽出するアプローチで解決しています。残念ながらSvelteKitではそのようなAPIは解放されていないよう。四苦八苦した末に、emotionの中のコードを調べました。

作成したスタイルは cache.registered というオブジェクトに全て格納されていて、cacheオブジェクトは共通インスタンスかつ export されていたのでそれをそのまま使うことに。

registeredは、{クラス名: CSS} というオブジェクトになっていたため、ちょっと無理やりであるもののテキストとしてCSSを生成して svelte:head で head に流し込む、というアプローチにしました。

もっと良いやり方ができれば書き換えたいですが、現時点だとこの方法ぐらいしか思いつかず...。ただ、種々のライブラリと苦戦する苦労を思えば単純明快で、思いのほか汎用性がある... かもしれないです。