htmx レスポンスヘッダー早見表:HX-Redirect/HX-Refresh/HX-Location の使い分け
htmx でサーバーからクライアントの画面遷移を制御する際、どのレスポンスヘッダーを使えばいいのか迷われたことはありませんか?ページ全体をリダイレクトしたいのか、現在のページを再読み込みしたいのか、それとも特定の要素だけを更新したいのか――目的によって使うべきヘッダーが変わります。
本記事では、htmx が提供する主要な 3 つのレスポンスヘッダー HX-Redirect、HX-Refresh、HX-Location の違いと使い分けを、実践的なコード例とともに解説します。それぞれの特性を理解することで、より柔軟で直感的な Web アプリケーションを構築できるようになるでしょう。
レスポンスヘッダー早見表
以下の表で、3 つのヘッダーの特徴を一目で確認できます。
| # | ヘッダー名 | 動作 | ブラウザ履歴 | 使用場面 |
|---|---|---|---|---|
| 1 | HX-Redirect | ページ全体をリダイレクト | 履歴に追加 | ログイン後のダッシュボード遷移、完全な画面切り替え |
| 2 | HX-Refresh | 現在のページを再読み込み | 履歴変更なし | フォーム送信後のページ更新、データ同期 |
| 3 | HX-Location | クライアント側ルーティング(pushState) | 履歴に追加 | SPA 風の遷移、特定要素の更新 |
| # | ヘッダー名 | htmx リクエスト | JavaScript 実行 | target 指定 |
|---|---|---|---|---|
| 1 | HX-Redirect | 無効化(通常のページ遷移) | ページロード時に実行 | 不可 |
| 2 | HX-Refresh | 無効化(ページリロード) | ページロード時に実行 | 不可 |
| 3 | HX-Location | 有効(htmx リクエストとして処理) | 既存の JavaScript 維持 | 可能(JSON で指定) |
背景
htmx におけるサーバー主導の画面制御
htmx は「HTML over the wire」という思想に基づき、サーバーから HTML を直接返すことで動的な Web アプリケーションを構築するライブラリです。従来の SPA(シングルページアプリケーション)のようにクライアント側で複雑なルーティングやステート管理をするのではなく、サーバー側が主導権を持って画面遷移を制御します。
この設計思想により、JavaScript を最小限に抑えながらも、リッチなユーザー体験を提供できるのが大きな魅力ですね。
レスポンスヘッダーによる遷移制御の必要性
htmx では、AJAX リクエストの結果として返される HTML を指定した要素に挿入するのが基本動作です。しかし、実際のアプリケーション開発では、以下のようなケースに遭遇します。
- フォーム送信後に別のページへ遷移したい
- 認証が必要な操作で、未ログイン時にログインページへリダイレクトしたい
- データ更新後にページ全体を最新状態に更新したい
これらのニーズに応えるため、htmx は特殊なレスポンスヘッダーを用意しています。
以下の図は、htmx リクエストとレスポンスヘッダーの関係を示したものです。
mermaidflowchart TB
client["ブラウザ<br/>(htmx)"]
server["サーバー"]
client -->|"HTTPリクエスト<br/>HX-Request: true"| server
server -->|"レスポンス<br/>+ 特殊ヘッダー"| client
subgraph response["レスポンスヘッダー"]
redirect["HX-Redirect<br/>(ページ全体遷移)"]
refresh["HX-Refresh<br/>(ページ再読み込み)"]
location["HX-Location<br/>(クライアント側遷移)"]
end
server -.->|選択| response
response -.-> client
サーバーは状況に応じて適切なヘッダーを選択し、クライアントの動作を制御します。
課題
適切なヘッダー選択の難しさ
htmx を初めて使う開発者が直面する課題の一つが、「どのヘッダーをいつ使うべきか」という判断です。一見すると似たような動作に見えるこれらのヘッダーですが、実際には明確な違いがあります。
誤ったヘッダーを使うと、以下のような問題が発生する可能性があるのです。
- ブラウザの戻るボタンが期待通りに動作しない
- ページの状態が意図せずリセットされる
- htmx の動的な動作が失われ、通常のページ遷移になってしまう
ドキュメントの情報が散在している
htmx の公式ドキュメントには各ヘッダーの説明がありますが、「どう使い分けるか」という実践的な情報は断片的です。開発者は複数のページを行き来しながら、自分で判断基準を組み立てる必要があります。
特に、HX-Location の JSON 形式での指定方法や、target 属性との組み合わせについては、理解するまでに時間がかかることが多いでしょう。
以下の図は、ヘッダー選択を誤った場合の問題を示しています。
mermaidflowchart LR
subgraph wrong["誤った選択"]
case1["認証エラー時に<br/>HX-Refreshを使用"]
case2["フォーム送信後に<br/>HX-Locationを使用<br/>(全画面遷移が必要なのに)"]
case3["部分更新したいのに<br/>HX-Redirectを使用"]
end
subgraph problems["発生する問題"]
prob1["ログインページへ<br/>遷移できない"]
prob2["ページ状態が<br/>リセットされる"]
prob3["htmx動作が<br/>無効化される"]
end
case1 --> prob1
case2 --> prob2
case3 --> prob3
適切なヘッダーを選ばないと、ユーザー体験が損なわれてしまいます。
解決策
HX-Redirect:ページ全体をリダイレクト
HX-Redirect は、ブラウザに対して通常の HTTP リダイレクトと同じ動作を指示するヘッダーです。このヘッダーを受け取ると、htmx は AJAX リクエストを中断し、ブラウザ全体を指定された URL へリダイレクトします。
主な特徴
- ページ全体が新しい URL へ遷移します
- ブラウザの履歴に新しいエントリが追加されます
- htmx の動的な動作は無効化され、通常のページロードになります
- すべての JavaScript の状態がリセットされます
使用すべきケース
- ログイン成功後のダッシュボードへの遷移
- フォーム送信完了後の完了ページへの遷移
- 権限エラー時の別ページへのリダイレクト
- アプリケーション全体のコンテキストが変わる場合
HX-Refresh:現在のページを再読み込み
HX-Refresh は、現在のページを完全にリロードするよう指示するヘッダーです。true という値を設定すると、ブラウザは現在の URL を再度読み込みます。
主な特徴
- 現在の URL が再読み込みされます
- ブラウザの履歴には新しいエントリが追加されません
- すべての JavaScript の状態がリセットされます
- サーバー側で最新のデータを取得できます
使用すべきケース
- フォーム送信後に同じページを最新状態で表示したい
- キャッシュをクリアして最新データを取得したい
- ページ全体の状態を初期化したい
- 複数の要素を一括で更新したい
HX-Location:クライアント側ルーティング
HX-Location は、最も柔軟で強力なヘッダーです。クライアント側のルーティング(history.pushState)を使用して、htmx の動的な動作を維持したまま画面遷移を実現します。
主な特徴
- htmx のリクエストとして処理されます
- ブラウザの履歴に新しいエントリが追加されます
- JavaScript の状態が維持されます(ページリロードなし)
- JSON 形式で詳細な制御が可能です
使用すべきケース
- SPA 風の滑らかな画面遷移を実現したい
- 特定の要素だけを更新しながら URL を変更したい
- JavaScript の状態を維持したまま遷移したい
- 動的なコンテンツ更新とナビゲーションを組み合わせたい
以下の図は、3 つのヘッダーの動作フローを比較したものです。
mermaidflowchart TD
request["htmx リクエスト"]
request --> redirect["HX-Redirect"]
request --> refresh["HX-Refresh"]
request --> loc["HX-Location"]
redirect --> redir_action["ブラウザ全体を<br/>新しいURLへ遷移"]
redir_action --> redir_result["・履歴に追加<br/>・JavaScript リセット<br/>・通常のページロード"]
refresh --> refresh_action["現在のURLを<br/>再読み込み"]
refresh_action --> refresh_result["・履歴変更なし<br/>・JavaScript リセット<br/>・最新データ取得"]
loc --> loc_action["pushState で<br/>URL変更"]
loc_action --> loc_result["・履歴に追加<br/>・JavaScript 維持<br/>・htmx で部分更新"]
用途に応じて、適切なヘッダーを選択することが重要です。
具体例
HX-Redirect の実装例
ログイン処理後にダッシュボードへリダイレクトする例を見ていきましょう。
サーバー側の実装(Node.js + Express)
javascript// ログインエンドポイント
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 認証処理
const user = await authenticateUser(username, password);
認証が成功した場合、HX-Redirect ヘッダーを使ってダッシュボードへリダイレクトします。
javascriptif (user) {
// セッションにユーザー情報を保存
req.session.userId = user.id;
// HX-Redirectヘッダーでリダイレクト
res.setHeader('HX-Redirect', '/dashboard');
res.status(200).end();
}
認証に失敗した場合は、エラーメッセージを含む HTML を返します。
javascript else {
// 認証失敗時はエラーメッセージを返す
res.status(401).send(`
<div class="error">
ユーザー名またはパスワードが正しくありません
</div>
`);
}
});
クライアント側の HTML
html<!-- ログインフォーム -->
<form hx-post="/login" hx-target="#login-form">
<div id="login-form">
<input
type="text"
name="username"
placeholder="ユーザー名"
required
/>
<input
type="password"
name="password"
placeholder="パスワード"
required
/>
<button type="submit">ログイン</button>
</div>
</form>
このコードでは、フォーム送信時に /login へ POST リクエストが送信されます。認証に成功すると、ブラウザ全体が /dashboard へリダイレクトされますね。
HX-Refresh の実装例
設定変更フォームの送信後、ページ全体を最新状態にリロードする例です。
サーバー側の実装(Python + Flask)
pythonfrom flask import Flask, request, make_response
app = Flask(__name__)
@app.route('/settings', methods=['POST'])
def update_settings():
# フォームデータを取得
theme = request.form.get('theme')
language = request.form.get('language')
設定をデータベースに保存した後、HX-Refresh ヘッダーを設定します。
python # データベースに設定を保存
save_user_settings(
user_id=session.get('user_id'),
theme=theme,
language=language
)
# HX-Refreshヘッダーでページ再読み込み
response = make_response('', 200)
response.headers['HX-Refresh'] = 'true'
return response
クライアント側の HTML
html<!-- 設定フォーム -->
<form hx-post="/settings" hx-target="this">
<h3>表示設定</h3>
<label>
テーマ:
<select name="theme">
<option value="light">ライト</option>
<option value="dark">ダーク</option>
</select>
</label>
<label>
言語:
<select name="language">
<option value="ja">日本語</option>
<option value="en">English</option>
</select>
</label>
<button type="submit">保存</button>
</form>
フォーム送信後、ページ全体が再読み込みされ、すべての要素が最新の設定で表示されます。テーマや言語の変更が即座に反映されるため、ユーザーは変更結果を確実に確認できるでしょう。
HX-Location の実装例(文字列形式)
商品一覧から商品詳細へ遷移する、シンプルな例から見ていきます。
サーバー側の実装(Node.js + Express)
javascript// 商品詳細リクエスト
app.get('/product/:id/details', (req, res) => {
const productId = req.params.id;
// htmxリクエストの場合
if (req.headers['hx-request']) {
htmx リクエストの場合、部分的な HTML のみを返し、HX-Location で URL を更新します。
javascript // 商品データを取得
const product = getProductById(productId);
// HX-Locationヘッダーで URL を更新
res.setHeader('HX-Location', `/product/${productId}`);
// 詳細部分のHTMLのみを返す
res.send(`
<div id="product-detail">
<h2>${product.name}</h2>
<p class="price">¥${product.price.toLocaleString()}</p>
<p>${product.description}</p>
<button hx-post="/cart/add/${productId}">
カートに追加
</button>
</div>
`);
}
通常のブラウザアクセスの場合は、ページ全体を返します。
javascript else {
// 通常のアクセスの場合はページ全体を返す
res.render('product-detail', { productId });
}
});
クライアント側の HTML
html<!-- 商品一覧 -->
<div id="main-content">
<div class="product-list">
<div class="product-card">
<img src="/images/product1.jpg" alt="商品1" />
<h3>商品1</h3>
<button
hx-get="/product/1/details"
hx-target="#main-content"
hx-push-url="false"
>
詳細を見る
</button>
</div>
</div>
</div>
このコードでは、hx-push-url="false" を設定しているため、htmx は URL を更新しません。しかし、サーバーから HX-Location ヘッダーが返ってくると、URL が /product/1 に更新されます。
HX-Location の実装例(JSON 形式)
より高度な制御が必要な場合、HX-Location に JSON 形式を使用できます。これにより、更新対象の要素や遷移アニメーションなどを細かく指定できるのです。
サーバー側の実装(Node.js + Express)
javascript// タブ切り替えのエンドポイント
app.get('/dashboard/:tab', (req, res) => {
const tab = req.params.tab; // 'overview', 'analytics', 'settings' など
// タブコンテンツを取得
const content = getDashboardContent(tab);
HX-Location に JSON を設定する場合は、オブジェクトを JSON 文字列に変換します。
javascript// HX-LocationをJSON形式で設定
const locationConfig = {
path: `/dashboard/${tab}`, // 遷移先URL
target: '#dashboard-content', // 更新する要素
swap: 'innerHTML', // 更新方法
};
res.setHeader(
'HX-Location',
JSON.stringify(locationConfig)
);
タブコンテンツの HTML を返します。
javascript // タブコンテンツのHTMLを返す
res.send(`
<div class="tab-content">
<h2>${content.title}</h2>
${content.html}
</div>
`);
});
クライアント側の HTML
html<!-- ダッシュボード -->
<div class="dashboard">
<nav class="tabs">
<button
hx-get="/dashboard/overview"
hx-target="#dashboard-content"
>
概要
</button>
<button
hx-get="/dashboard/analytics"
hx-target="#dashboard-content"
>
分析
</button>
<button
hx-get="/dashboard/settings"
hx-target="#dashboard-content"
>
設定
</button>
</nav>
<div id="dashboard-content">
<!-- タブコンテンツがここに表示される -->
</div>
</div>
タブをクリックすると、#dashboard-content の内容のみが更新され、URL も /dashboard/analytics のように変更されます。ページ全体はリロードされないため、スムーズな画面遷移を実現できるでしょう。
複数ヘッダーの組み合わせパターン
実際のアプリケーションでは、条件に応じて異なるヘッダーを返す必要があります。以下は、認証状態に応じて適切なヘッダーを選択する例です。
認証チェックを含むエンドポイント
javascript// コメント投稿エンドポイント
app.post('/article/:id/comment', async (req, res) => {
const articleId = req.params.id;
const { content } = req.body;
// 認証チェック
if (!req.session.userId) {
未ログインの場合は、ログインページへリダイレクトします。
javascript // 未ログインの場合はログインページへリダイレクト
res.setHeader('HX-Redirect', '/login');
res.status(401).end();
return;
}
コメントの保存に成功した場合は、ページをリフレッシュします。
javascript try {
// コメントを保存
await saveComment({
articleId,
userId: req.session.userId,
content
});
// 成功時はページをリフレッシュして最新のコメント一覧を表示
res.setHeader('HX-Refresh', 'true');
res.status(200).end();
}
エラーが発生した場合は、エラーメッセージを返します。
javascript catch (error) {
// エラー時はエラーメッセージを返す
res.status(500).send(`
<div class="error">
コメントの投稿に失敗しました。もう一度お試しください。
</div>
`);
}
});
このように、状況に応じて適切なヘッダーを返すことで、ユーザー体験を向上させることができます。
以下の図は、条件分岐による適切なヘッダー選択のフローを示しています。
mermaidflowchart TD
start["リクエスト受信"]
start --> auth_check{"認証状態<br/>チェック"}
auth_check -->|未ログイン| use_redirect["HX-Redirect<br/>使用"]
use_redirect --> redirect_login["ログインページへ<br/>リダイレクト"]
auth_check -->|ログイン済み| process["処理実行"]
process --> success_check{"処理結果"}
success_check -->|成功| use_refresh["HX-Refresh<br/>使用"]
use_refresh --> refresh_page["ページ再読み込みで<br/>最新データ表示"]
success_check -->|エラー| return_error["エラーHTML<br/>返却"]
return_error --> show_error["エラーメッセージ<br/>表示"]
認証状態と処理結果に応じて、最適なヘッダーを選択することが重要です。
まとめ
htmx のレスポンスヘッダー HX-Redirect、HX-Refresh、HX-Location は、それぞれ異なる目的で設計されています。適切なヘッダーを選ぶことで、ユーザーにとって直感的で快適な Web アプリケーションを構築できるのです。
選択の基本原則
- 完全な画面切り替えが必要なら HX-Redirect:ログイン後の遷移や、コンテキストが大きく変わる場面で使用します
- 最新データの一括取得が必要なら HX-Refresh:フォーム送信後の状態更新や、複数要素の同期が必要な場面で活用しましょう
- 滑らかな部分更新を実現するなら HX-Location:SPA 風の画面遷移や、JavaScript 状態を維持したい場面で威力を発揮します
これらのヘッダーを使いこなすことで、サーバー側から柔軟に画面遷移を制御できます。htmx の思想である「HTML over the wire」を活かしながら、リッチなユーザー体験を提供できるでしょう。
最初は判断に迷うかもしれませんが、本記事の早見表と具体例を参考にしていただければ、すぐに適切な選択ができるようになります。実際のプロジェクトで試しながら、それぞれのヘッダーの特性を体感してみてください。
関連リンク
articlehtmx レスポンスヘッダー早見表:HX-Redirect/HX-Refresh/HX-Location の使い分け
articlehtmx × Laravel/PHP 導入手順:Blade パーシャルとルート設計の落とし穴回避
articlehtmx と Stimulus/Turbo の責務分担を実験で検証:同一要件 3 実装レビュー
articlehtmx 運用 SLO 設計:サーバ応答・スワップ完了・履歴遷移の 3 指標を可視化
articlehtmx で二重送信が起きる/起きない問題の完全対処:trigger と disable パターン
articlehtmx で管理画面 CRUD を 10 倍速に:一覧・検索・編集・バルク操作テンプレ
articlegpt-oss 推論パラメータ早見表:temperature・top_p・repetition_penalty...その他まとめ
articleLangChain を使わない判断基準:素の API/関数呼び出しで十分なケースと見極めポイント
articleJotai エコシステム最前線:公式&コミュニティ拡張の地図と選び方
articleGPT-5 監査可能な生成系:プロンプト/ツール実行/出力のトレーサビリティ設計
articleFlutter の描画性能を検証:リスト 1 万件・画像大量・アニメ多用の実測レポート
articleJest が得意/不得意な領域を整理:単体・契約・統合・E2E の住み分け最新指針
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来