htmx 逆引きレシピ
リンク/フォームをboostして“準SPA”にするには?
公開日:
最終更新日:
既存のSSRサイトでも、ページ遷移のたびに全体が再読み込みされると「待ち」が増えてストレスになります。
htmxの hx-boost を使うと、リンククリックやフォーム送信を“部分更新”に変えて、準SPAのような体験にできます。
このページでは「既存SSRに後付け」「遷移を速く」「段階的にhtmxへ寄せる」の3デモを用意しました。
hx-select / hx-target / hx-swap と組み合わせて、業務UIでもそのまま使える形に落とし込みます。
使用するhtmx属性
hx-boost:リンククリック/フォーム送信をAJAX化し、ページ遷移を“準SPA”っぽくするhx-select:レスポンスHTMLから「必要な部分だけ」を抜き出して使う(例:#MAINだけ)hx-target:差し替える先(どこに反映するか)を指定するhx-swap:差し替え方を指定する(例:outerHTMLで丸ごと置換)
利用シーン
- 「既存SSRに後付け」:まずは“全部作り直さずに”部分遷移だけ導入したい
- 「遷移を速く」:一覧→詳細、検索→結果など、よく使う導線だけ軽くしたい
- 「段階的にhtmxへ寄せる」:一気にSPA化せず、少しずつ置き換えたい
① 既存SSRに後付け
既存のSSRページに hx-boost を付けるだけで、リンク/フォーム遷移を“準SPA”にできます。
このデモでは <main> だけ差し替え、ヘッダー/フッターはSSRのまま残します。
PHP
<?php
// 型を厳密に扱う
declare(strict_types=1);
// HTMLエスケープ関数を用意する
function h(string $s): string {
// 特殊文字をエスケープして返す
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// 表示するページ種別を受け取る(home / customers / settings)
$page = (string)($_GET['page'] ?? 'home');
// 検索キーワードを受け取る(顧客ページ用)
$q = (string)($_GET['q'] ?? '');
// 前後空白を除去する
$page = trim($page);
// 前後空白を除去する
$q = trim($q);
// 有効なページ一覧を定義する
$allowed = ['home', 'customers', 'settings'];
// 不正なpageならhomeに戻す
if (!in_array($page, $allowed, true)) $page = 'home';
// 顧客一覧(ダミー)を用意する
$customers = [
['id' => 101, 'name' => '田中商事', 'plan' => 'STANDARD'],
['id' => 102, 'name' => 'みかん工業', 'plan' => 'PRO'],
['id' => 103, 'name' => '鈴木電機', 'plan' => 'STANDARD'],
['id' => 104, 'name' => '佐藤物流', 'plan' => 'ENTERPRISE'],
];
// 検索後に表示する顧客一覧を入れる配列
$filtered = [];
// customersページなら検索フィルタする
if ($page === 'customers') {
// 1件ずつ見る
foreach ($customers as $c) {
// 検索が空なら全部入れる
if ($q === '') {
$filtered[] = $c;
continue;
}
// 名前に部分一致したら入れる
if (mb_stripos((string)$c['name'], $q) !== false) {
$filtered[] = $c;
continue;
}
}
}
// タイトルを決める
$titleMap = [
'home' => 'ホーム',
'customers' => '顧客一覧',
'settings' => '設定',
];
// 表示タイトルを決める
$pageTitle = $titleMap[$page] ?? 'ホーム';
?>
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?= h($pageTitle) ?> | hx-boost(既存SSRに後付け)</title>
</head>
<body>
<header class="HEADER">
<h1>デモ①:既存SSRに後付け(最小の準SPA化)</h1>
<p class="HTMX-NOTE">ヘッダーはSSRのまま固定、<code><main></code> だけをboostして部分遷移にします。</p>
</header>
<!--
ここがポイント
- hx-boost="true":リンク/フォームをAJAX化
- hx-target="#BOOST1_MAIN":差し替え先はmain自身
- hx-select="#BOOST1_MAIN":レスポンスからmainだけ抜く
- hx-swap="outerHTML":mainを丸ごと置換(入れ子事故を防ぐ)
-->
<main
id="BOOST1_MAIN"
hx-boost="true"
hx-target="#BOOST1_MAIN"
hx-select="#BOOST1_MAIN"
hx-swap="outerHTML"
>
<nav class="NAV">
<a class="BTN" href="/htmx/demo/_boost_ssr.php?page=home">ホーム</a>
<a class="BTN" href="/htmx/demo/_boost_ssr.php?page=customers">顧客一覧</a>
<a class="BTN" href="/htmx/demo/_boost_ssr.php?page=settings">設定</a>
</nav>
<section class="SECTION">
<h2><?= h($pageTitle) ?></h2>
<?php if ($page === 'home'): ?>
<p>
これは“普通のSSRページ”ですが、<code>hx-boost</code> によってリンク遷移が部分更新になります。<br>
まずはこういう「後付け」から始めると、段階的に快適化できます。
</p>
<?php endif; ?>
<?php if ($page === 'customers'): ?>
<p>
フォーム送信もboost対象です。検索(GET)してもページ全体は再読み込みされず、<code><main></code> だけが差し替わります。
</p>
<form method="get" class="FORM">
<input type="hidden" name="page" value="customers">
<label>
検索
<input type="text" name="q" value="<?= h($q) ?>" placeholder="会社名で検索">
</label>
<button class="BTN is-ok" type="submit">検索</button>
</form>
<table class="TABLE">
<thead>
<tr><th>ID</th><th>会社名</th><th>プラン</th></tr>
</thead>
<tbody>
<?php foreach ($filtered as $c): ?>
<tr>
<td><?= h((string)$c['id']) ?></td>
<td><?= h((string)$c['name']) ?></td>
<td><span class="BADGE"><?= h((string)$c['plan']) ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if ($page === 'settings'): ?>
<p>
設定ページのような「フォーム中心の画面」でも、遷移体験をそのまま準SPAにできます。<br>
(このデモでは内容はダミーです)
</p>
<ul class="HTMX-LIST">
<li>通知:ON</li>
<li>テーマ:LIGHT</li>
<li>権限:ADMIN</li>
</ul>
<?php endif; ?>
</section>
</main>
<footer class="FOOTER">
<p class="HTMX-NOTE">フッターもSSRのまま固定です(mainだけ差し替え)。</p>
</footer>
</body>
</html>
デモ
① 既存SSRに後付け
解説
HTMLでやっていること
<main>にhx-boost="true"を付け、リンククリック/フォーム送信をAJAX化します。hx-target="#BOOST1_MAIN"で、差し替え先をmain自身に指定します。hx-select="#BOOST1_MAIN"で、レスポンスHTMLからmainだけ抜き出して使います。hx-swap="outerHTML"でmainを丸ごと置換し、入れ子崩れを防ぎます。- リンク/フォームは普通の
href/method="get"のままなので、非JS環境でもSSRとして動きます。
PHPでやっていること
$_GET['page']で表示ページ(home/customers/settings)を切り替えます。- 顧客ページでは
$_GET['q']を受け取り、配列データを部分一致でフィルタします。 - SSRとしてページ全体(ヘッダー/フッター含む)を返しますが、htmx側が
mainだけ抽出して反映します。
② 遷移を速く(一覧→詳細を部分更新)
よく使う導線(一覧→詳細)だけを部分更新にすると、体感速度が一気に上がります。
このデモでは「一覧はそのまま」「右の詳細パネルだけ差し替え」でサクサク遷移します。
PHP
<?php
// 型を厳密に扱う
declare(strict_types=1);
// HTMLエスケープ関数を用意する
function h(string $s): string {
// 特殊文字をエスケープして返す
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// 検索キーワードを受け取る
$q = (string)($_GET['q'] ?? '');
// 詳細IDを受け取る
$id = (string)($_GET['id'] ?? '');
// 前後空白を除去する
$q = trim($q);
// 前後空白を除去する
$id = trim($id);
// ダミーデータ(申請一覧)を用意する
$rows = [
['id' => '801', 'title' => '申請:端末貸与', 'owner' => 'tanaka', 'status' => 'OPEN'],
['id' => '802', 'title' => '申請:権限変更', 'owner' => 'sato', 'status' => 'PENDING'],
['id' => '803', 'title' => '申請:アカウント追加', 'owner' => 'suzuki', 'status' => 'DONE'],
['id' => '804', 'title' => '申請:VPN追加', 'owner' => 'tanaka', 'status' => 'OPEN'],
];
// 表示用の一覧を入れる配列
$list = [];
// 1件ずつ見る
foreach ($rows as $r) {
// 検索が空なら全部入れる
if ($q === '') {
$list[] = $r;
continue;
}
// titleに部分一致したら入れる
if (mb_stripos((string)$r['title'], $q) !== false) {
$list[] = $r;
continue;
}
}
// 選択中の詳細を初期化する
$detail = null;
// idが指定されていれば探す
if ($id !== '') {
// 1件ずつ探す
foreach ($rows as $r) {
// 一致したら採用する
if ((string)$r['id'] === $id) {
$detail = $r;
break;
}
}
}
?>
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>遷移を速く(一覧→詳細) | hx-boost</title>
</head>
<body>
<header class="HEADER">
<h1>デモ②:遷移を速く(一覧→詳細を部分更新)</h1>
<p class="HTMX-NOTE">一覧は残し、右の詳細パネルだけを入れ替えます。</p>
</header>
<div class="GRID-2COL">
<!-- 左:一覧(検索・リンクはboost) -->
<section
id="BOOST2_LIST"
class="SECTION"
hx-boost="true"
hx-target="#BOOST2_LIST"
hx-select="#BOOST2_LIST"
hx-swap="outerHTML"
>
<h2>申請一覧</h2>
<form method="get" class="FORM">
<label>
検索
<input type="text" name="q" value="<?= h($q) ?>" placeholder="タイトルで検索">
</label>
<button class="BTN is-ok" type="submit">検索</button>
</form>
<table class="TABLE">
<thead>
<tr><th>ID</th><th>タイトル</th><th>担当</th><th>ステータス</th></tr>
</thead>
<tbody>
<?php foreach ($list as $r): ?>
<tr>
<td><?= h((string)$r['id']) ?></td>
<!--
ここがポイント
- 一覧はboostで部分更新(検索など)
- “詳細リンクだけ”は右パネルをtargetにする
-->
<td>
<a
href="/htmx/demo/_boost_fast.php?q=<?= h($q) ?>&id=<?= h((string)$r['id']) ?>"
hx-target="#BOOST2_DETAIL"
hx-select="#BOOST2_DETAIL"
hx-swap="outerHTML"
><?= h((string)$r['title']) ?></a>
</td>
<td><?= h((string)$r['owner']) ?></td>
<td><span class="BADGE"><?= h((string)$r['status']) ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<!-- 右:詳細パネル(ここだけ差し替えられる) -->
<section id="BOOST2_DETAIL" class="SECTION">
<h2>詳細</h2>
<?php if ($detail === null): ?>
<p class="HTMX-NOTE">左の一覧から1件選ぶと、このパネルだけ更新されます。</p>
<?php else: ?>
<ul class="HTMX-LIST">
<li><strong>ID:</strong><?= h((string)$detail['id']) ?></li>
<li><strong>タイトル:</strong><?= h((string)$detail['title']) ?></li>
<li><strong>担当:</strong><?= h((string)$detail['owner']) ?></li>
<li><strong>ステータス:</strong><?= h((string)$detail['status']) ?></li>
</ul>
<?php endif; ?>
</section>
</div>
</body>
</html>
デモ
② 遷移を速く(一覧→詳細を部分更新)
解説
HTMLでやっていること
- 一覧エリア(左カラム)に
hx-boostを付け、検索フォームの遷移を部分更新にします。 - 詳細リンクは
hx-target="#BOOST2_DETAIL"を指定し、右の詳細パネルだけを差し替えます。 - 詳細表示も
hx-select="#BOOST2_DETAIL"で必要部分だけ抜き出し、hx-swap="outerHTML"で丸ごと置換します。 - “全部の遷移”をboostするのではなく、“速くしたい導線だけ”ターゲットを分けるのがコツです。
PHPでやっていること
- ダミーの申請一覧配列を用意し、
$_GET['q']でタイトル検索を行います。 $_GET['id']が指定されていれば、その行の詳細データを探して右パネルに表示します。- 返しているのは普通のSSRページですが、htmxが「左だけ」「右だけ」を選んで差し替えます。
③ 段階的にhtmxへ寄せる
いきなり全画面を“準SPA化”しなくてもOKです。
まずは「このエリアだけ」boostし、徐々にhtmxに寄せる移行ができます。
PHP
<?php
// 型を厳密に扱う
declare(strict_types=1);
// HTMLエスケープ関数を用意する
function h(string $s): string {
// 特殊文字をエスケープして返す
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
// タブ種別を受け取る(profile / billing / export)
$tab = (string)($_GET['tab'] ?? 'profile');
// 前後空白を除去する
$tab = trim($tab);
// タブの許可リストを用意する
$allowed = ['profile', 'billing', 'export'];
// 不正なtabならprofileに戻す
if (!in_array($tab, $allowed, true)) $tab = 'profile';
// タブタイトルを用意する
$tabTitleMap = [
'profile' => 'プロフィール',
'billing' => '請求設定',
'export' => 'エクスポート',
];
// 表示タイトルを決める
$tabTitle = $tabTitleMap[$tab] ?? 'プロフィール';
?>
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>段階的にhtmxへ寄せる | hx-boost</title>
</head>
<body>
<header class="HEADER">
<h1>デモ③:段階的にhtmxへ寄せる(boost範囲を小さく始める)</h1>
<p class="HTMX-NOTE">
「この画面だけ速くしたい」など、狭い範囲から始められます。<br>
(このデモでは“中央の設定エリア”だけboostしています)
</p>
</header>
<!-- ここはSSRのまま(boostしない) -->
<nav class="NAV">
<a class="BTN" href="/htmx/demo/_boost_ssr.php?page=home">(SSR)ホームへ</a>
<a class="BTN" href="/htmx/demo/_boost_ssr.php?page=customers">(SSR)顧客へ</a>
</nav>
<!--
ここだけをboostして「部分遷移」を段階導入する
- hx-select/hx-target/hx-swap で #BOOST3_MAIN を丸ごと差し替え
-->
<main
id="BOOST3_MAIN"
class="SECTION"
hx-boost="true"
hx-target="#BOOST3_MAIN"
hx-select="#BOOST3_MAIN"
hx-swap="outerHTML"
>
<h2><?= h($tabTitle) ?></h2>
<nav class="NAV">
<a class="BTN" href="/htmx/demo/_boost_step.php?tab=profile">プロフィール</a>
<a class="BTN" href="/htmx/demo/_boost_step.php?tab=billing">請求設定</a>
<a class="BTN" href="/htmx/demo/_boost_step.php?tab=export">エクスポート</a>
</nav>
<?php if ($tab === 'profile'): ?>
<p>ここは設定エリアだけ部分遷移します。既存SSRに“後付け”しやすい導入形です。</p>
<ul class="HTMX-LIST">
<li>表示名:みかん</li>
<li>メール:mikan@example.com</li>
</ul>
<?php endif; ?>
<?php if ($tab === 'billing'): ?>
<p>タブ切り替えはリンク遷移ですが、<code>hx-boost</code> により準SPAのように切り替わります。</p>
<ul class="HTMX-LIST">
<li>支払い方法:クレジットカード</li>
<li>請求日:毎月末</li>
</ul>
<?php endif; ?>
<?php if ($tab === 'export'): ?>
<p>
“全リンクをboostしたくない”もの(ファイルDLなど)は、リンク単体で <code>hx-boost="false"</code> にできます。
</p>
<ul class="HTMX-LIST">
<li>
<a class="BTN" href="/htmx/demo/_dummy_export.csv" hx-boost="false">CSVダウンロード(SSRのまま)</a>
</li>
<li class="HTMX-NOTE">※このリンクはboostしない例です(実ファイルは任意で用意)</li>
</ul>
<?php endif; ?>
</main>
</body>
</html>
デモ
③ 段階的にhtmxへ寄せる
解説
HTMLでやっていること
#BOOST3_MAIN(設定エリア)だけにhx-boostを付け、タブ切り替えを部分遷移化します。hx-target="#BOOST3_MAIN"+hx-select="#BOOST3_MAIN"で、中央エリアだけ差し替える構成にします。hx-swap="outerHTML"でエリアを丸ごと置換し、内部DOMの崩れを防ぎます。- boostしたくないリンク(例:ファイルDL)は
hx-boost="false"で除外できます。
PHPでやっていること
$_GET['tab']を受け取り、profile/billing/export の表示を切り替えます。- タブ内容はSSRで生成しますが、差し替えは htmx が担当するため準SPAのように見えます。
- 導入初期は「一部エリアだけ boost」→慣れたら範囲拡大、という移行がしやすいです。
参考リンク
このページの著者
経験:Webアプリ/業務システム
得意:PHP・JavaScript・MySQL・CSS
個人実績:フォーム生成基盤/クイズ学習プラットフォーム 等
詳しいプロフィールはこちら! もちもちみかんのプロフィール