turbo について
Hotwire Turbo でサーバー描画ページに SPA のようなナビゲーションと更新を加える Symfony 開発者向けのリファレンススキルです。Drive、Frames、Streams を使った移動、部分更新、複数要素やリアルタイムの変更を扱います。
主な機能
- 01Turbo Drive による AJAX ナビゲーション
- 02Frames によるページの部分更新
- 03Streams による複数 DOM 要素の更新
- 04Frames の遅延読み込み
- 05SSE による Mercure ブロードキャスト
活用シーン
- 01リンクやフォーム操作での全ページ再読み込みの回避
- 02フォームからページの一部分を更新
- 03操作後に複数のページ要素を更新
- 04接続中のブラウザーへのリアルタイム変更の配信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ smnandre/ symfony-ux-skills --skill turboインストール後、Claude Code で/turboを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/api.mdgotchas.mdpatterns.md詳細
Turbo
Hotwire Turbo は、サーバーでレンダリングされた HTML によって、SPA のような速度を実現します。JavaScript を記述する必要はありません。3 つのコンポーネントが連携して動作します。
- Drive -- すべてのリンクとフォームを自動で AJAX ナビゲーションします(設定不要)
- Frames -- ページの 1 つのセクションだけを更新する、範囲を限定したナビゲーションです
- Streams -- サーバーからプッシュされる DOM の変更(追加、置換、削除など)です
判断フロー
Need to update the page?
+-- Full page navigation -> Turbo Drive (automatic, already active)
+-- Single section from user click -> Turbo Frame
+-- Multiple sections from action -> Turbo Stream (HTTP response)
+-- Real-time from server/others -> Turbo Stream (Mercure / SSE)
インストール
composer require symfony/ux-turbo
これで完了です。Turbo Drive はすぐに有効になり、すべてのリンクとフォームが AJAX になります。
Turbo Drive
SPA のような自動ナビゲーションです。すべての <a> のクリックと <form> の送信を捕捉し、AJAX で取得して、<body> を置き換えます。ブラウザーの URL と履歴は通常どおり更新されます。
特定の要素で無効にする
<a href="/external" data-turbo="false">External Link</a>
<div data-turbo="false">
<a href="/normal">Normal link (no Turbo)</a>
</div>
履歴とキャッシュ
<a href="/page" data-turbo-action="replace">Replace History</a>
<link rel="stylesheet" href="/app.css" data-turbo-track="reload">
<script src="/app.js" data-turbo-track="reload"></script>
Turbo Frames
ページのセクションにナビゲーションの範囲を限定します。フレーム内のリンクとフォームは、そのフレームのコンテンツだけを更新します。ページの残りの部分には影響しません。
基本的なフレーム
<turbo-frame id="messages">
<h2>Messages</h2>
<a href="/messages/1">View Message 1</a>
</turbo-frame>
<turbo-frame id="messages">
<h2>Message 1</h2>
<p>Content here...</p>
<a href="/messages">Back to list</a>
</turbo-frame>
サーバーのレスポンスは完全な HTML ページですが、Turbo は一致する <turbo-frame> だけを抽出して置き換えます。
遅延読み込み
ページのレンダリング後にフレームのコンテンツを非同期で読み込みます。
<turbo-frame id="notifications" src="/notifications" loading="lazy">
<p>Loading...</p>
</turbo-frame>
別のフレームをターゲットにする
あるフレーム内のリンクから、別のフレームを更新できます。
<turbo-frame id="sidebar">
<a href="/item/1" data-turbo-frame="main-content">View Item</a>
</turbo-frame>
<turbo-frame id="main-content">
</turbo-frame>
フレームから抜ける
フレーム内からページ全体をナビゲートします。
<turbo-frame id="modal">
<a href="/dashboard" data-turbo-frame="_top">Go to Dashboard</a>
</turbo-frame>
フォーム付きフレーム
フレーム内のフォームは、そのフレーム内で送信され、更新されます。
<turbo-frame id="search-results">
<form action="/search" method="get">
<input type="search" name="q">
<button>Search</button>
</form>
<ul>
{% for item in results %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</turbo-frame>
URL の同期
フレームのナビゲーション時にブラウザーの URL を更新します(ブックマーク可能な状態にする場合に便利です)。
<turbo-frame id="products" data-turbo-action="advance">
</turbo-frame>
Turbo Streams
1 つのサーバーレスポンスから、複数の DOM 要素を更新します。8 つのアクション(append、prepend、replace、update、remove、before、after、refresh)があり、ID または CSS セレクターで要素を指定します。
Stream アクション
<turbo-stream action="append" target="messages">
<template><div id="msg_1">New message</div></template>
</turbo-stream>
<turbo-stream action="prepend" target="messages">
<template><div id="msg_0">First!</div></template>
</turbo-stream>
<turbo-stream action="replace" target="notification">
<template><div id="notification">Updated!</div></template>
</turbo-stream>
<turbo-stream action="update" target="counter">
<template>42</template>
</turbo-stream>
<turbo-stream action="remove" target="msg_5"></turbo-stream>
<turbo-stream action="before" target="msg_3">
<template><div id="msg_2">Inserted before</div></template>
</turbo-stream>
<turbo-stream action="after" target="msg_3">
<template><div id="msg_4">Inserted after</div></template>
</turbo-stream>
<turbo-stream action="refresh"></turbo-stream>
replace と update では、完全に置き換える代わりにスムーズな DOM モーフィングを行うための、任意の method="morph" 属性を指定できます。
<turbo-stream action="replace" method="morph" target="user-card">
<template><div id="user-card">Updated content</div></template>
</turbo-stream>
複数の要素をターゲットにする(CSS セレクター)
targets(複数形)と CSS セレクターを使うと、複数の要素に作用させることができます。
<turbo-stream action="remove" targets=".notification.read"></turbo-stream>
<turbo-stream action="update" targets=".price">
<template>99.00 EUR</template>
</turbo-stream>
Streams 用 Twig コンポーネントの構文
Symfony UX 2.22 以降では、生の HTML の代わりに <twig:Turbo:Stream:*> コンポーネントを使用できます。
<twig:Turbo:Stream:Append target="comments">
{{ include('comment/_comment.html.twig') }}
</twig:Turbo:Stream:Append>
<twig:Turbo:Stream:Update target="comment-count">
{{ count }}
</twig:Turbo:Stream:Update>
<twig:Turbo:Stream:Remove target="msg_5" />
Symfony との統合
コントローラーからの Stream レスポンス
use Symfony\UX\Turbo\TurboBundle;
#[Route('/messages', name: 'message_create', methods: ['POST'])]
public function create(Request $request): Response
{
$message = new Message();
// ... handle form
$this->em->persist($message);
$this->em->flush();
// Return stream response for Turbo requests
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);
return $this->render('message/create.stream.html.twig', [
'message' => $message,
'count' => $count,
]);
}
プログラムで Stream を構築するには、TurboStreamResponse ヘルパーや TurboStream ヘルパーメソッドも使用できます。
Stream テンプレート
{# templates/message/create.stream.html.twig #}
<turbo-stream action="append" target="messages">
<template>
{{ include('message/_message.html.twig', {message: message}) }}
</template>
</turbo-stream>
<turbo-stream action="update" target="message-count">
<template>{{ count }}</template>
</turbo-stream>
<turbo-stream action="replace" target="new-message-form">
<template>
{{ include('message/_form.html.twig', {message: null}) }}
</template>
</turbo-stream>
フレームリクエストの検出
public function show(Request $request, int $id): Response
{
if ($request->headers->has('Turbo-Frame')) {
$frameId = $request->headers->get('Turbo-Frame');
// Return only the frame content (or a full page -- Turbo extracts the frame)
}
return $this->render('page/show.html.twig');
}
Mercure ブロードキャスト(リアルタイム)
SSE を介して、接続中のすべてのブラウザーに変更をプッシュします。
use Symfony\UX\Turbo\Attribute\Broadcast;
#[Broadcast]
class Message
{
// Entity changes broadcast automatically to subscribed clients
}
{# Subscribe to Mercure topic #}
<turbo-stream-source src="{{ mercure('chat-room-1')|escape('html_attr') }}">
</turbo-stream-source>
<div id="messages">
{# Messages appear here in real-time #}
</div>
よくあるパターン
インライン編集
<turbo-frame id="task_{{ task.id }}">
<span>{{ task.title }}</span>
<a href="/tasks/{{ task.id }}/edit">Edit</a>
</turbo-frame>
<turbo-frame id="task_1">
<form action="/tasks/1" method="post">
<input name="title" value="Task title">
<button>Save</button>
<a href="/tasks/1">Cancel</a>
</form>
</turbo-frame>
フレーム内のモーダル
<turbo-frame id="modal"></turbo-frame>
<a href="/items/1/delete" data-turbo-frame="modal">Delete</a>
<turbo-frame id="modal">
<dialog open>
<p>Confirm delete?</p>
<form method="post">
<button>Delete</button>
</form>
<a href="/items" data-turbo-frame="modal">Cancel</a>
</dialog>
</turbo-frame>
Stream を使ったフラッシュメッセージ
<turbo-stream action="prepend" target="flash-messages">
<template>
<div class="alert alert-success" role="alert">
Item saved successfully!
</div>
</template>
</turbo-stream>
重要な原則
サーバーは完全な HTML ページを返します。 サーバーが常に完全で有効な HTML ページを返す場合に、Turbo は最も効果を発揮します。Turbo Drive は body を置き換え、Turbo Frames は一致するフレームを抽出します。部分的な HTML スニペットを返そうとしないでください(Stream テンプレートを除く)。
フレームの ID は一致している必要があります。 レスポンス内のフレームは、ページ上のフレームと同じ id を持つ必要があります。一致しない場合、Turbo はエラーを表示します。
Streams は副作用に使用します。 1 つのアクションで、ページ内の関連しない複数の部分を更新する必要がある場合に Streams を使用します。1 つのセクションだけを更新する場合は、Frame のほうがシンプルです。
Stimulus は Turbo を補完します。 Turbo はナビゲーションとサーバー通信を処理します。Stimulus はクライアントサイドの動作(アニメーション、切り替え、クリップボード)を処理します。両者は連携して動作します。Stimulus コントローラーは、そのスコープ内での Turbo Frame の置換後も維持され、Drive のナビゲーション時には適切に再接続されます。
参考資料
- 完全な API(Drive のイベント、Frame の属性、Stream のアクション、Mercure):references/api.md
- パターン(フォーム、モーダル、検索、ページネーション、無限スクロール):references/patterns.md
- 注意点(キャッシュの問題、フォームの処理、Stimulus との統合):references/gotchas.md
関連項目
- UX Map は Turbo Frames 内で動作します。マップの Stimulus コントローラーは、フレームの入れ替え後に正しく再接続されます。
- UX Icons はインライン SVG であり、特別な処理なしで Turbo Drive のナビゲーションや Frame の入れ替え後も維持されます。