Remix 3 発表まとめ - React を捨て、Web標準で新しい世界へ
はじめに
2025年10月10日、カナダのトロントで開催されたイベント "Remix Jam 2025" で Ryan Florence と Michael Jackson が Remix 3 を発表しました。このセッションは、React Router の生みの親たちが、なぜ React から離れ、独自のフレームワークを作ることにしたのか、その理由と新しいビジョンを語った歴史的な発表です。
本記事では、1時間47分に及ぶセッションの内容を詳しく解説します。
なぜ Remix 3 を作るのか
React への感謝と決別
Michael Jackson と Ryan Florence は、React に対して深い敬意を持っています。React は彼らのキャリアを変え、Web 開発の考え方を一変させました。React Router を10年以上メンテナンスし、Shopify のような大企業がそれに依存しています。
しかし、ここ1〜2年、彼らは React の方向性に違和感を感じるようになりました。
「僕らはもう、React がどこに向かっているのか分からなくなってきた」- Michael Jackson
現代のフロントエンド開発の複雑さ
Ryan は、フロントエンドエコシステムの複雑さについて率直に語ります:
「正直言って、全体像を把握できなくなってきた。フロントエンド開発者として、自分でも何が起きているのか分からない時がある」- Ryan Florence
彼らは、この状況を「山を登る」比喩で表現しています:
「僕らはこの山を登ってきて、頂上でキャンプしようとしている。でも、登ってきたおかげで視野が広がり、別の山が見えてきた。もっとシンプルな山が。だから、この山を下りて、あっちの山に登り直すことにした」- Ryan Florence
Web プラットフォームの進化
Node.js は16歳、React は12〜13歳です。その間に Web プラットフォームは大きく進化しました:
- ES Modules: ブラウザでモジュールをロードできる
- TypeScript: 型による開発体験の向上
- Service Workers: バックエンド機能をブラウザで
- Web Streams: Node.js にも標準ストリームが
- Fetch API: Node.js でも使える
- Web Crypto: 暗号化機能が標準に
AI 時代のフレームワーク
Ryan は、AI 時代のフレームワークに必要な要素について語ります:
- 安定した URL: LLM がアクションを実行するため、URL は常に同じである必要がある
- シンプルなコード: AI が生成・理解しやすいコード
- バンドラーへの依存を減らす: ランタイムセマンティクスがバンドラーに依存しない
React の use server
では、RPC 関数の URL がビルドごとに変わってしまうため、AI がそれを利用することが困難です。
Remix 3 の核心アイデア
セットアップスコープ (Setup Scope)
Remix 3 の最も革新的な概念が Setup Scope(セットアップスコープ) です。
import { events } from "@remix-run/events"
import { tempo } from "./01-intro/tempo"
import { createRoot, type Remix } from "@remix-run/dom"
function App(this: Remix.Handle) {
// このスコープは1回だけ実行される(セットアップスコープ)
let bpm = 60;
// レンダー関数を返す
return () => (
<button
on={tempo((event) => {
bpm = event.detail
this.update()
})}
>
BPM: {bpm}
</button>
);
};
createRoot(document.body).render(<App />)
重要なポイント:
- セットアップコードは1回だけ実行される
- 状態は JavaScript のクロージャに保存される(Remix の特別な機能ではない)
- 再レンダリングは
this.update()
を明示的に呼ぶ
「ボタンはどうやって BPM が変わったことを知るの? 知らない。それが Remix 3 の素晴らしいところ。これはただの JavaScript スコープ。君が
update()
を呼んだ時だけ、レンダー関数を再実行する」- Ryan Florence
Remix Events: イベントを第一級市民に
Remix 3 では、イベントをコンポーネントと同じレベルの抽象化として扱います。
click
イベントの複雑さ
Ryan は、click
イベントの複雑さを説明します:
- マウスダウン + マウスアップ(同じ要素上)
- キーボードの Space ダウン + Space アップ(Escape なし)
- キーボードの Enter ダウン(即座にクリック + リピート)
- タッチスタート + タッチアップ(スワイプなし)
これらすべてが click
イベントです。
カスタムインタラクションの作成
Remix Events を使うと、独自のインタラクションを作成できます:
import { createInteraction, events } from "@remix-run/events"
import { pressDown } from "@remix-run/events/press"
export const tempo = createInteraction<HTMLElement, number>(
"rmx:tempo",
({ target, dispatch }) => {
let taps: number[] = []
let resetTimer: number = 0
function handleTap() {
clearTimeout(resetTimer)
taps.push(Date.now())
taps = taps.filter((tap) => Date.now() - tap < 4000)
if (taps.length >= 4) {
let intervals = [];
for (let i = 1; i < taps.length; i++) {
intervals.push(taps[i] - taps[i - 1])
}
let bpm = intervals.map(
(interval) => 60000 / interval
)
let avgTempo = Math.round(
bpm.reduce((sum, value) => sum + value, 0) /
bpm.length
)
dispatch({ detail: avgTempo })
}
resetTimer = window.setTimeout(() => {
taps = []
}, 4000)
}
return events(target, [pressDown(handleTap)])
}
)
使い方:
<button on={tempo((event) => {
bpm = event.detail
this.update();
})}>
BPM: {bpm}
</button>
「コンポーネントが要素に対する抽象化であるように、カスタムインタラクションはイベントに対する抽象化だ」- Ryan Florence
図: Components are to elements as custom interactions are to events
Context API: 再レンダリングを引き起こさない
Remix 3 の Context API は、React とは根本的に異なります。
// コンテキストを提供
function App(this: RemixHandle<{ drummer: Drummer }>) {
const drummer = new Drummer();
// コンテキストをセット(レンダリングは発生しない)
this.context.set({ drummer });
return function render() {
return <DrumMachine />;
};
}
// コンテキストを取得
function TempoDisplay(this: RemixHandle) {
// 型安全にコンテキストを取得
const drummer = this.context.get(App).drummer;
// drummer の変更を購読
drummer.addEventListener('change', this.update);
return function render() {
return <div>{drummer.bpm} BPM</div>;
};
}
重要なポイント:
context.set()
は再レンダリングを引き起こさない-
context.get(Component)
でプロバイダーを直接参照("Go to Definition" が効く!) - 型安全: プロバイダーコンポーネントの型から自動推論
図: Go to Definition でプロバイダーに直接ジャンプできる
Signal: 非同期処理の管理
Remix 3 には重要な原則があります:
「関数を渡したら、signal を返す」
イベントハンドラーには自動的に signal
が渡されます(AbortController
の signal):
<select
on:change={async (event, signal) => {
state = 'loading';
this.update();
// signal を fetch に渡す
const response = await fetch(`/api/cities?state=${event.target.value}`, {
signal
});
// 古いリクエストは自動的に中断される
if (signal.aborted) return;
cities = await response.json();
state = 'loaded';
this.update();
}}
>
ユーザーが連続してセレクトボックスを変更すると:
- 古いハンドラーの signal が abort される
-
fetch()
が自動的にキャンセルされる -
signal.aborted
チェックで古い処理をスキップ
図: ネットワークタブで古いリクエストがキャンセルされている様子
これにより、レースコンディションを手動で、しかしシンプルに解決できます。
実際のデモから学ぶ
デモ1: カウンターアプリ
最もシンプルな例から始まります。
// プレーンな DOM API から始める
const button = document.createElement('button')
let count = 0
button.textContent = `Count: ${count}`
button.onclick = () => {
count++
button.textContent = `Count: ${count}`
};
これを Remix 3 のコンポーネントに:
function Counter(this: RemixHandle) {
let count = 0;
return function render() {
return (
<button
on:click={() => {
count++
this.update()
}}
>
Count: {count}
</button>
);
};
}
「山を下りているんだ。プラットフォームには何がある?」- Ryan Florence
図: シンプルなカウンターの実装
デモ2: BPM (Tempo Tapper) アプリ
ボタンをタップして BPM(テンポ)を計算するアプリです。
function TempoTapper(this: RemixHandle) {
let bpm = 60;
return function render() {
return (
<button
on:tempo={(e) => {
bpm = e.detail;
this.update();
}}
>
{bpm} BPM
</button>
);
};
}
tempo
カスタムインタラクションが、複雑なタップ計算ロジックをカプセル化しています。コンポーネントは結果を受け取るだけです。
図: タップ間隔を計算して平均BPMを算出
デモ3: ドラムマシン
完全なドラムマシンアプリを構築します。
主な機能:
- Play/Stop ボタン
- テンポ調整(BPM)
- ビジュアライザー(音量表示)
- キーボードショートカット(Space: 再生/停止、Arrow Up/Down: テンポ変更)
Drummer クラス(AI が生成)
class Drummer extends EventTarget {
#bpm = 90;
play(bpm) {
this.#bpm = bpm;
// ドラムサウンドを再生...
this.dispatchEvent(new CustomEvent('change'));
}
stop() {
// 再生を停止...
this.dispatchEvent(new CustomEvent('change'));
}
set bpm(value) {
this.#bpm = value;
this.dispatchEvent(new CustomEvent('change'));
}
get bpm() {
return this.#bpm;
}
}
「Cursor に『キック、スネア、ハイハットを持ったドラマーを作って』って頼んだら、こいつが吐き出してくれた。最高だろ?」- Ryan Florence
キーボードイベントの統合
import { space, arrowUp, arrowDown } from '@remix/events';
function App(this: RemixHandle) {
const drummer = new Drummer();
return function render() {
return (
<div
on:window={[
[space, () => drummer.toggle()],
[arrowUp, () => drummer.bpm += 5],
[arrowDown, () => drummer.bpm -= 5],
]}
>
<DrumMachine />
</div>
);
};
}
window
にイベントを追加しているのに、コンポーネント内のコードと変わりません。
図: Space、Arrow Up/Down でドラムマシンを操作
デモ4: フォームと非同期処理
州を選択すると、その州の都市リストを fetch する典型的な UI です。
function CitySelector(this: RemixHandle) {
let state = 'idle';
let cities = [];
return function render() {
return (
<form>
<select
on:change={async (event, signal) => {
state = 'loading';
this.update();
const response = await fetch(
`/api/cities?state=${event.target.value}`,
{ signal }
);
if (signal.aborted) return;
cities = await response.json();
state = 'loaded';
this.update();
}}
>
<option>Alabama</option>
<option>Alaska</option>
{/* ... */}
</select>
<select disabled={state === 'loading'}>
{cities.map(city => (
<option>{city}</option>
))}
</select>
</form>
);
};
}
「イベントから考え始める。それが僕のやり方。ユーザーが最初のセレクトボックスを変更した → ローディング状態にする → データを取得 → ロード完了。これが一番自然な考え方だと思わない?」- Ryan Florence
図: 連続して選択を変更した場合の問題
Remix 3 の設計思想
抽象化は最小限に
「抽象化は、本当に必要だと感じるまで導入しない。イベントには型安全性と合成のために必要だった。でも、他の部分は?」- Ryan Florence
Remix 3 のコンポーネントは、特別な状態管理ライブラリを使いません:
let bpm = 60; // ただの変数
更新も明示的:
this.update(); // これだけ
Web 標準を最大限活用
-
EventTarget
とCustomEvent
-
AbortController
とsignal
-
PointerEvent
でマウス・タッチ・ペンを統一 - DOM API をそのまま利用
TypeScript ファーストの開発体験
「Remix 1 と 2 では TypeScript はサイドクエストみたいなものだった。でも今は、TypeScript が開発体験の中心だ」- Michael Jackson
すべての API が型安全に設計されています:
- イベントの detail 型
- Context の型推論
- コンポーネントの props 型
LLM で生成しやすいコード
Ryan は、AI が Drummer クラスを生成したことを何度も強調します。Remix 3 のコードは:
- シンプルで予測可能
- 特殊な規則が少ない
- Web 標準に基づいている
そのため、LLM が理解・生成しやすいのです。
React Router は継続される
重要なポイント:
- React Router は継続されます
- Shopify など多くの企業が React Router に依存
- Remix チームが React Router V7 を開発中
- Remix 3 は別の選択肢として提供
「React Router はどこにも行かない。それだけは明確にしておきたい」- Ryan Florence
現在のステータス
- プロトタイプ段階
- ブログ投稿の3ヶ月後に開発開始
- 個別パッケージとして公開中(
@remix/events
、@remix/ui
など) - 最終的には統合されたフレームワークとして提供予定
- コンポーネントライブラリも開発中(ドロップダウンメニュー、テーマシステムなど)
図: Remix UI コンポーネントライブラリのプレビュー
まとめ
Remix 3 は、フロントエンド開発の複雑さに対するアンチテーゼです。
主要な特徴:
- Setup Scope: JavaScript のクロージャを活用した状態管理
- Remix Events: イベントを第一級市民として扱う
-
明示的な再レンダリング:
this.update()
で制御 - 型安全な Context API: 再レンダリングを引き起こさない
- Signal による非同期管理: レースコンディションをシンプルに解決
- Web 標準ベース: バンドラーへの依存を最小化
- TypeScript ファースト: すべての API が型安全
- AI フレンドリー: LLM が理解・生成しやすいコード
Ryan と Michael のメッセージ:
「3ヶ月間、日の光を見ていない。でも、これはワクワクする。僕らは正しい山を見つけたと思う」- Ryan Florence
Remix 3 は、Web 開発の未来を再定義しようとしています。シンプルさ、Web 標準、型安全性、そして AI との親和性。これらすべてを兼ね備えた新しいフレームワークの登場を、期待して待ちましょう。
参考リンク
この記事が役に立ったら、ぜひ実際のセッション動画もご覧ください。Ryan のライブコーディングと軽妙なトークは、文字では伝えきれない魅力があります!
Discussion