---
title: "Remix SPAモードとは？ssr:falseの設定と制約、React Router移行の判断"
url: "https://www.issoh.co.jp/tech/details/3840/"
published: 2024-10-15
updated: 2026-08-07
categories: ["React"]
publisher: "株式会社一創"
---

# Remix SPAモードとは？ssr:falseの設定と制約、React Router移行の判断

Remixでサーバーを持たずに静的ホスティングへ配信したいとき、選択肢になるのがSPAモードです。ただしこのモードは「サーバーレンダリングをやめるだけ」ではありません。`loader` と `action` の export はビルドエラーになり、初期HTMLにはルートルートより深い内容が入りません。さらに2024年11月以降、Remix v2の後継はReact Router v7へ移り、SPAモードの仕様もそちらで先に更新されています。ここでは公式ドキュメントとnpmの公開版で確認した仕様に絞り、設定手順・制約・移行判断を整理します。

## まとめ：Remix SPAモードの要点

- SPAモードはRemix 2.5.0で追加された機能で、ViteおよびRemix Viteプラグインの利用が前提です。
- 有効化は `vite.config.ts` で `ssr: false` を指定するだけですが（2.5.x／2.6.x では `unstable_ssr`）、その瞬間に `headers`・`loader`・`action` は export した時点でビルドが失敗します。
- データ取得と更新は `clientLoader`・`clientAction` に一本化されます。これらから `serverLoader`・`serverAction` は呼び出せません。
- `index.html` はルートルートの `HydrateFallback` をビルド時に事前レンダリングして生成されます。ルート以下のページ内容は初期HTMLに含まれません。
- 新規プロジェクトでRemix v2のSPAモードを選ぶ理由は2026年8月時点でほぼありません。後継のReact Router（最新 8.3.0）ではルートルートの `loader` が使えるなど制約が緩和されています。

以降で、設定・制約・デプロイ・SEO・移行判断の順に、公式ドキュメントの記述と対応させながら見ていきます。

## SPAモードの位置づけと2.5.0での追加背景

Remixは本来「サーバーアーキテクチャは開発者が所有する」という前提のフレームワークです。それでも公式ドキュメントは、サーバーを運用したくない場合、GitHub PagesやCDNへ静的ファイルとして配信したい場合、サーバーレンダリングできない組み込み用途などを現実に存在する妥当なユースケースとして挙げ、そのためにSPAモードを2.5.0で追加したと説明しています。

ドキュメントはSPAモードの実体を「React Router + Vite で `createBrowserRouter`／`RouterProvider` を自前構成した場合に得られるもの」に、ファイルベースルーティング、ルート単位の自動コード分割、`<Link prefetch>`、`<Meta>`／`<Links>` による head 管理を足したものと位置づけています。つまりSPAモードは別系統の機能ではなく、Remixの規約をクライアント側だけで走らせる構成です。

## ssr:falseによる有効化とindex.htmlの生成

### vite.config.tsでの指定とテンプレートからの生成

既存のRemix+Viteアプリでは、Remix Viteプラグインの設定に `ssr: false` を渡します。

```
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    remix({
      ssr: false,
    }),
  ],
});
```

ここでバージョンを確認してください。このフラグ名は途中で変わっています。SPAモードが追加された2.5.0では `unstable_ssr` で、v2.7.0のCHANGELOGにある「Vite: Stabilize “SPA Mode” by renaming the Remix vite plugin config from `unstable_ssr` \-> `ssr`」で現在の名前に落ち着きました。2.5.x系や2.6.x系を使ったまま `ssr: false` と書いても効きません。

新規なら公式テンプレートを使うほうが確実です。開発サーバーは通常のRemixと同じく `npx remix vite:dev` で起動し、HMRもそのまま効きます。

```
npx create-remix@latest --template remix-run/remix/templates/spa
```

### HydrateFallbackから生成されるindex.html

`npx remix vite:build` を実行すると、Remixは `/` のサーバーハンドラを呼び出し、レンダリング結果を既定で `build/client/index.html` に保存します。この `index.html` は `root.tsx` の `HydrateFallback` コンポーネントから生成されます。

```
export function HydrateFallback() {
  return (
    <>
      <p>Loading...</p>
      <Scripts />
    </>
  );
}

export default function Component() {
  return (
    <>
      <Outlet />
      <Scripts />
    </>
  );
}
```

ここで押さえるべきは、公式ドキュメントの「index.htmlを生成する初回レンダリングにはrootより深いルートは一切含まれない」という記述です。これは欠陥ではなく設計で、生成された1枚の `index.html` を `/about` のような任意のパスでも配信・ハイドレートできるようにするための割り切りです。`<Scripts>` を `HydrateFallback` に置き忘れると、生成されたHTMLからJavaScriptの読み込みが落ちてハイドレート自体が始まりません。

## SPAモードで使えなくなるサーバーAPIの境界

ここが最も誤解の多い部分です。SPAモードでは、次の制約が公式に明記されています。

- `headers`・`loader`・`action` といったサーバーAPIは使用できず、export した時点でビルドがエラーを投げます。
- `HydrateFallback` を export できるのは `root.tsx` だけで、他のルートから export するとビルドがエラーを投げます。
- `clientLoader`／`clientAction` から `serverLoader`／`serverAction` は呼び出せません。動かすサーバーが存在しないためです。

「SPAモードでもloaderでデータを取得する」と説明している記事や、SPAモードの章に `export let loader` を載せているサンプルは、この時点で成立しません。ビルドが通らないため、コピーしてもアプリは起動しません。

## clientLoader・clientActionによるデータ取得と更新

データ取得と更新は Client Data API に一本化されます。書き方はサーバー版とほぼ同じで、関数名が変わります。

```
export async function clientLoader({ params }) {
  const res = await fetch(`/api/items/${params.id}`);
  return res.json();
}

export async function clientAction({ request }) {
  const formData = await request.formData();
  const res = await fetch("/api/items", {
    method: "POST",
    body: formData,
  });
  return res.json();
}
```

React Routerアプリから移行する場合、公式が示す手順も「すべての `loader`／`action` を `clientLoader`／`clientAction` にリネームする」ことです。関数の引数や戻り値の扱いはサーバー版と共通なので、両者の役割分担は[React Routerのaction・loaderとclientAction・clientLoaderの違い](/tech/details/4436/)の整理がそのまま使えます。

設計上の注意は1点あります。APIキーを伴う外部サービス呼び出しやデータベース直結など、サーバー側でしか実行できない処理はSPAモードに置き場所がありません。別途API層を用意する前提になります。

## SPAフォールバック設定とビルド時レンダリングの落とし穴

### すべてのパスをindex.htmlへ向ける配信設定

生成物は静的ファイルなので任意のHTTPサーバーで配信できますが、単一の `index.html` を複数パスに対して返す設定（SPAフォールバック）が必要です。公式は簡易例として sirv-cli を挙げています。

```
npx sirv-cli build/client/ --single
```

ホスティングサービスによっては `_redirects` のようなファイルで同じ指定を行います。有効なはずのURLで404が出る場合、まず疑うのはこの設定です。Cloudflare Pagesへ載せる構成は[RemixとCloudflare Pagesを使ったサイト構成のセットアップ手順](/tech/details/3438/)で扱っています。AWS Amplify Hostingに載せる場合は書き換え規則の記法が独特なので、[Remix SPAモードとAWS Amplify Gen 2の構成手順](/tech/details/2281/)を参照してください。

### ビルド時サーバーレンダリングによるwindow・document参照エラー

SPAを作っているつもりでも、`index.html` を生成するためにビルド時のサーバーレンダリングは走ります。Remix v2のドキュメントは「SPAを作っているとはいえ、ビルド工程としてのサーバービルドとサーバーレンダリングは依然として存在する」と明言したうえで、`document`・`window`・`localStorage` のようなクライアント専用APIを参照する依存関係への注意を促しています。回避策として挙げられているのは、ブラウザ専用ライブラリを `entry.client.tsx` からインポートしてサーバービルドに含めない方法、`React.lazy` の利用、remix-utils の `<ClientOnly>` コンポーネントの3つです。依存パッケージのCJS/ESM問題で落ちる場合は、Viteの `ssr.noExternal` に該当パッケージを指定してサーバービルドへバンドルします。

## SPAモードのSEO上の限界とhead管理

### 初期HTMLがルートシェルだけになる制約

SPAモードには実行時のサーバーレンダリングが存在しません。配信されるHTMLは、ビルド時にルートルートから生成した1枚だけです。したがって「SPAモードでもSSRによってSEOを強化できる」という説明は成立しません。クローラが最初に受け取るHTMLには、記事本文も商品情報もタイトルの出し分けも含まれず、レンダリング後にしか現れません。

検索流入が事業要件に入っているなら、SPAモードは選ぶべきではありません。判断基準は単純で、URLごとに異なる内容が検索対象になるならサーバーレンダリングか事前レンダリングを使い、認証後の管理画面や社内ツールのようにインデックスさせないものだけをSPAモードに寄せる、という切り分けになります。

### 事前レンダリングを併用する場合の緩和

React Router側では `prerender` オプションを併用できます。対象パスを配列で列挙するか、非同期関数で返します。

```
import type { Config } from "@react-router/dev/config";

let slugs = getPostSlugs();

export default {
  ssr: false,
  prerender: ["/", "/blog", ...slugs.map((s) => `/blog/${s}`)],
} satisfies Config;
```

ビルドすると、パスごとに初回ドキュメント要求用の `[url].html` と、クライアント側遷移が読む `[url].data` が出力されます。このとき制約が一部緩み、事前レンダリング対象のパスにマッチするルートは `loader` を持てます。公式ドキュメントの注記どおり、`action` と `headers` は `ssr: false` のままである限り引き続き使えません。URLを有限に列挙できるコンテンツであれば、この組み合わせが現実的な落としどころです。

### MetaDescriptor配列で返すv2のmeta仕様

head管理は `<Meta>`／`<Links>` APIで行いますが、Remix v2の `meta` はオブジェクトではなく MetaDescriptor の配列を返します。v1形式の `return { title, description }` はv2では機能しません。

```
export const meta: MetaFunction = () => {
  return [
    { title: "Very cool app | Remix" },
    { property: "og:title", content: "Very cool app" },
    { name: "description", content: "This app is the best" },
  ];
};
```

公式ドキュメントはこれらの記述子について「HTMLタグと1対1で対応する」と説明しています。`{ title }` は `<title>` タグを生成し、description は `name` と `content` のペアで書きます。スタイルシートやファビコンは `meta` ではなく `links` の担当です。設定の全体像は[React Routerのmeta・title設定と基本ルーティング](/tech/details/7618/)と共通しています。

## Remix v2のSPAモードを新規採用すべきでない条件

### React Router v7への移行手順とcodemodの適用範囲

React Router公式の移行ドキュメントは、v7を「Remix v2の次のメジャーバージョン」と明記しています。実際、`remix.run/docs/...` へアクセスすると旧版扱いの `v2.remix.run` へリダイレクトされ、そのページ上部には React Router v7 のリリース告知が常時表示されます。Remix v2のfutureフラグをすべて有効にしてあれば、移行作業の中心は依存パッケージとimport元の入れ替えです。`@remix-run/node` などランタイム固有パッケージに再エクスポートされていた共通APIは `react-router` へ集約されました。

```
npx codemod remix/2/react-router/upgrade
```

このcodemodは依存の更新、import元の書き換え、`package.json` のスクリプト変更（`remix vite:dev` → `react-router dev`、`remix vite:build` → `react-router build`）、エントリファイルの `RemixServer` → `ServerRouter`／`RemixBrowser` → `HydratedRouter` のリネームまで自動化します。

要件は移行先の系列で変わります。v7は node@20 / react@18 / react-dom@18 が下限ですが、最新のv8は `node@22.22` 以上、`react@19.2.7`／`react-dom@19.2.7` 以上を要求し、フレームワークモードではさらに `vite@7` 以上（`future.v8_viteEnvironmentApi` の有効化を伴います）が必要です。2026年8月3日時点のnpm公開版は @remix-run/react が 2.17.5、react-router が 8.3.0（2026年7月22日公開、v8.0.0は同年6月17日）で、v7系も 7.18.2（2026年7月28日公開）として保守されています。React 19系へ上げられないプロジェクトは、まずv7系へ寄せるのが現実的です。移行のタイミングでルーター自体を見直すなら、[React/SPA向けの型安全ルーターTanStack Router](/tech/details/9913/)も比較対象に入ります。

なおRemixという名前は、v2の延長ではない新しいフレームワーク「Remix 3」を指すようになりました。npm の `remix` パッケージは 3.0.0-beta.5（dist-tag は `next`、2026年7月1日）が最新で、`latest` は 2.17.5 のままです。設計思想は[Web標準ベースで組み直されたRemix 3のコンポーネントライブラリ](/tech/details/10504/)で扱っています。

### React Router 7.2.0以降のルートloader対応

移行によってSPAモードの制約そのものが緩みます。設定先は `react-router.config.ts` に変わります。

```
import { type Config } from "@react-router/dev/config";

export default {
  ssr: false,
} satisfies Config;
```

React Router 7.2.0（2025年2月18日公開）のCHANGELOGにある「Allow a root `loader` in SPA Mode」により、ルートルートに限り `loader` を書けるようになりました。公式ドキュメントは `ssr:false` の効果を「実行時のサーバーレンダリングだけを無効化する。ルートルートはビルド時にサーバーレンダリングされて `index.html` を生成する」と説明しており、ルートの `loader` はそのビルド時に実行され、結果を `HydrateFallback` の `loaderData` プロパティから受け取れます。Remix v2では `loader` の export そのものがビルドエラーだったので、挙動の違いを理解せずに移行するとここで戸惑います。

v8へ上げる場合は `meta` 周りの破壊的変更も併せて確認してください。`meta` と `matches` の `data` が廃止されて `loaderData` に統一され、`react-router-dom` パッケージ自体がなくなってDOM固有APIは `react-router/dom` からのimportになります。Cloudflare環境では `cloudflareDevProxy` を `@cloudflare/vite-plugin` に置き換えます。

### SPAモードに関係する既知脆弱性と修正バージョン

SPAモードと事前レンダリングの周辺では、修正済みの脆弱性が複数公表されています。バージョンを固定したまま運用している場合は確認が要ります。

| 識別子            | 内容                       | react-router          | 修正版    | @remix-run/react  | 修正版    |
| -------------- | ------------------------ | --------------------- | ------ | ----------------- | ------ |
| CVE-2025-43864 | SPAモード強制のキャッシュポイズニングでDoS | 7.2.0以上 7.5.1以下       | 7.5.2  | 対象外               | –      |
| CVE-2025-43865 | 事前レンダリングデータのなりすまし        | 7.0.0-pre.0以上 7.5.1以下 | 7.5.2  | 対象外               | –      |
| CVE-2025-59057 | script:ld+json生成時のXSS    | 7.0.0以上 7.8.2以下       | 7.9.0  | 1.15.0以上 2.17.0以下 | 2.17.1 |
| CVE-2026-21884 | ScrollRestoration経由のXSS  | 7.0.0以上 7.12.0未満      | 7.12.0 | 2.17.3未満          | 2.17.3 |

いずれもGitHub Security Advisoryでの深刻度はHighです。前2件は2025年4月24日、後2件は2026年1月8日の公表で、後2件はサーバーレンダリング時に発火します。CVE-2026-21884のアドバイザリは「フレームワークモードでサーバーレンダリングを無効化しているアプリケーションには影響しない」と明記しており、`ssr: false` の構成はこの点で有利です。裏を返せば、SSRへ戻す判断をした時点でこれらは自分の問題になります。

ただし上表は、SPAモードと事前レンダリングに直接関わるものだけを抜き出したものです。react-routerには2026年6月以降もDoSやRCE相当のアドバイザリが継続して公表されており（turbo-stream経由の任意コンストラクタ呼び出しは7.14.2、経路マッチングのDoSは7.18.0で修正）、上表の修正版に上げれば十分という意味ではありません。v7系なら7.18.2、新規なら8.3.0というように、系列内の最新パッチへ追随してください。なお8.3.0で修正されたRSCモードのCSRF（GHSA-qwww-vcr4-c8h2、2026年7月24日公表）はv7系に修正版がありませんが、アドバイザリが対象を不安定版のRSC APIに限定しているため、それを使っていなければv7系に留まる判断は成立します。

## よくある質問

### Remix SPAモードとReact RouterのSPAモードは何が違いますか？

設定ファイルが `vite.config.ts` から `react-router.config.ts` へ変わり、React Router 7.2.0以降はルートルートに `loader` を書けます。Remix v2では `loader` のexportはビルドエラーでした。加えてReact Router側では `prerender` を併用でき、事前レンダリング対象パスのルートは `loader` を持てます。

### SPAモードでloaderを使うとどうなりますか？

ビルドがエラーで停止します。公式ドキュメントは `headers`・`loader`・`action` をexportするとビルドがエラーを投げると明記しています。データ取得は `clientLoader`、更新は `clientAction` を使ってください。これらから `serverLoader`・`serverAction` を呼ぶこともできません。

### SPAモードでもSEO対策はできますか？

初期HTMLはルートルートから生成した1枚だけなので、URLごとの内容は初期HTMLに含まれません。検索流入が必要なページは、サーバーレンダリングに戻すか、React Routerの `prerender` で対象パスを静的HTML化するのが現実的です。事前レンダリングではパスごとに `[url].html` と `[url].data` が生成されます。

### 既存のRemix v2 SPAアプリはReact Routerへ移行すべきですか？

futureフラグを有効にしてあれば、移行の中心は依存とimportの入れ替えで、`npx codemod remix/2/react-router/upgrade` が用意されています。判断の分かれ目は要件で、v7は node 20 / React 18 以上、v8は node 22.22 / React 19.2.7 以上が必要です。React 19系へ上げられないうちは、v7系の最新パッチに留めるのが安全です。

### ビルド結果はどこに出力されますか？

既定で `build/client/` です。`index.html` もこの直下に生成されます。React Router側でも同じで、`react-router build` の実行後は `build/client` ディレクトリを静的ホストへ配置します。`ssr: false` ではデプロイ対象のサーバーバンドルが出力されないため、Nodeプロセスを常駐させる必要はありません。ビルド工程としてのサーバーレンダリングが走る点とは別の話です。

## 関連記事

- [React Routerのaction・loaderの使い方｜clientAction/clientLoaderの違いとv8対応](/tech/details/4436/)
- [React Routerのmeta・title設定と基本ルーティング｜v8対応入門](/tech/details/7618/)
- [RemixとCloudflare Pagesを使ったWebサイト構成のセットアップ方法](/tech/details/3438/)
- [Remix 3 の新しいコンポーネントライブラリとは何か：Web標準ベースのUIコンポーネント設計を解説](/tech/details/10504/)
- [TanStack Routerとは何か？概要・特徴・メリットとReact/SPA開発で注目される理由を解説](/tech/details/9913/)

---

出典: [Remix SPAモードとは？ssr:falseの設定と制約、React Router移行の判断](<https://www.issoh.co.jp/tech/details/3840/>)（株式会社一創）
