@k8ordo/state

ハイドレーションの前に読む

テーマのクラスのように、最初の描画より前に<html>へ反映したい値があります。useAppStateはハイドレーションのあとに動くので、それでは間に合いません。このページでは、localStorageやsessionStorageの状態を、インラインスクリプトで先に読む方法を説明します。

このページの内容

キーを手で書かない

よくあるのは、保存キーとJSONの形をスクリプトの文字列に直接書く方法です。しかしそれでは、キーや保存の形が変わった瞬間に、定義と食い違ってしまいます。

defineLocalStateとdefineSessionStateの定義は、そのどちらも持っています。storageKeyはストアが書き込むキーで、inlineRead()はインラインの<script>に埋め込むJavaScriptの式を返します。この式は、ブラウザで評価されると、その定義の置き場所に保存されたオブジェクトになります。

inlineRead()があるのは、localStorageとsessionStorageの定義だけです。Cookieの状態なら、リクエストを受け取るページでサーバーがparseCookiesで読み、最初の描画から本当の値で描けます。

inlineReadを埋め込む

inlineRead()が返すのは式なので、スクリプトの中で値として使います。

state.ts
import { defineLocalState } from '@k8ordo/state';
import * as z from 'zod';

export const themeState = defineLocalState(
  'theme',
  z.object({ mode: z.enum(['light', 'dark']).optional() }),
);
routes/layout.tsx
import type { ReactNode } from 'react';

import { themeState } from '../state';

const applyTheme = `(() => {
  const s = ${themeState.inlineRead()};
  if (s && s.mode === 'dark') {
    document.documentElement.classList.add('dark');
  }
})();`;

export default function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <script>{applyTheme}</script>
      </head>
      <body>{children}</body>
    </html>
  );
}

スクリプトはReactより先に<html>のクラスを変えるので、<html>にはsuppressHydrationWarningを付けます。ハイドレーションのあとは、ストアの値を正として扱ってください。

式は即時実行の関数なので、代入の右辺でも、関数の引数でも、三項演算子の中でも使えます。キーは<も含めてスクリプトの中で安全な形にエスケープされるので、どんなキーでも埋め込めます。

nullになるとき

式は投げません。次のときは、nullになります。

  • 何も保存されていない
  • 保存されたJSONが壊れている
  • 値がオブジェクトではない(数や文字列、配列、null)
  • ストレージそのものが読めない
  • versionを持つlocalStorageの状態で、行がほかの版で書かれている

返ってくるのは生の行

スクリプトが動く時点では、まだどのモジュールも読み込まれていません。そのためスキーマは走らず、返ってくるのはフィールドごとに拾った状態ではなく、保存されていた生の行です。

中身を信頼せず、必要なフィールドだけを、それぞれ自分で確かめて読んでください。上の例がs && s.mode === 'dark'と書いているのは、そのためです。

情報

メモ

@k8ordo/color-schemeは、このinlineRead()を使って、最初の描画の前に<html>へクラスを付けています。カラースキームを切り替えるなら、自分で書かずにこのパッケージを使ってください。

k8ordo

Baselineに入った機能を、制限なく使うReactのライブラリ群。

© 2026 k8o — MIT License

組版:Noto Sans JP / M PLUS 2