T-CREATOR

htmx レスポンスヘッダー早見表:HX-Redirect/HX-Refresh/HX-Location の使い分け

htmx レスポンスヘッダー早見表:HX-Redirect/HX-Refresh/HX-Location の使い分け

htmx でサーバーからクライアントの画面遷移を制御する際、どのレスポンスヘッダーを使えばいいのか迷われたことはありませんか?ページ全体をリダイレクトしたいのか、現在のページを再読み込みしたいのか、それとも特定の要素だけを更新したいのか――目的によって使うべきヘッダーが変わります。

本記事では、htmx が提供する主要な 3 つのレスポンスヘッダー HX-RedirectHX-RefreshHX-Location の違いと使い分けを、実践的なコード例とともに解説します。それぞれの特性を理解することで、より柔軟で直感的な Web アプリケーションを構築できるようになるでしょう。

レスポンスヘッダー早見表

以下の表で、3 つのヘッダーの特徴を一目で確認できます。

#ヘッダー名動作ブラウザ履歴使用場面
1HX-Redirectページ全体をリダイレクト履歴に追加ログイン後のダッシュボード遷移、完全な画面切り替え
2HX-Refresh現在のページを再読み込み履歴変更なしフォーム送信後のページ更新、データ同期
3HX-Locationクライアント側ルーティング(pushState)履歴に追加SPA 風の遷移、特定要素の更新
#ヘッダー名htmx リクエストJavaScript 実行target 指定
1HX-Redirect無効化(通常のページ遷移)ページロード時に実行不可
2HX-Refresh無効化(ページリロード)ページロード時に実行不可
3HX-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-RedirectHX-RefreshHX-Location は、それぞれ異なる目的で設計されています。適切なヘッダーを選ぶことで、ユーザーにとって直感的で快適な Web アプリケーションを構築できるのです。

選択の基本原則

  • 完全な画面切り替えが必要なら HX-Redirect:ログイン後の遷移や、コンテキストが大きく変わる場面で使用します
  • 最新データの一括取得が必要なら HX-Refresh:フォーム送信後の状態更新や、複数要素の同期が必要な場面で活用しましょう
  • 滑らかな部分更新を実現するなら HX-Location:SPA 風の画面遷移や、JavaScript 状態を維持したい場面で威力を発揮します

これらのヘッダーを使いこなすことで、サーバー側から柔軟に画面遷移を制御できます。htmx の思想である「HTML over the wire」を活かしながら、リッチなユーザー体験を提供できるでしょう。

最初は判断に迷うかもしれませんが、本記事の早見表と具体例を参考にしていただければ、すぐに適切な選択ができるようになります。実際のプロジェクトで試しながら、それぞれのヘッダーの特性を体感してみてください。

関連リンク