Rails製APIにて、JavaScriptの fetch() に対してリダイレクト的なステータスコードを返したい時はどうすればよいか調べてみた

Railsのコントローラにてリダイレクトをしたい時は redirect_to が使えます。
2.3 redirect_toを使う | レイアウトとレンダリング - Railsガイド

以下の例では、コントローラの show() へルーティングされた時に after_redirect_erb_fruits_path へとリダイレクトしています。

class AfterRedirect::Erb::FruitsController < ApplicationController
  def index
  end

  def show
    redirect_to after_redirect_erb_fruits_path  # リダイレクト
  end
end

 
試しに curl で動作確認すると、HTTP302なレスポンスになります。

% curl http://localhost:7100/after_redirect/erb/fruits/1 -v
...
< HTTP/1.1 302 Found
...
< Location: http://localhost:7100/after_redirect/erb/fruits
...
<html><body>You are being <a href="http://localhost:7100/after_redirect/erb/fruits">redirected</a>.</body></html>

 
そんな中

  • バックエンドはRailsAPIを作る
  • フロントエンドのJavaScriptfetch() RailsアプリのAPIを呼ぶ
    • APIのレスポンスを元に、JavaScript側で処理を加えたりリダイレクトする

というアプリを作ってみたところ、 fetch() で受け取ったレスポンスがcurlと異なっていました。

そこで、レスポンスの違いについて調べたことをメモしておきます。

 

目次

 

環境

  • Rails 7.0.3
  • React 18.1.0
    • JavaScriptfetch() を使うためのアプリをReactで作成

 
なお、コントローラは

class Api::TryRedirect::FruitsController < ApplicationController
  def show
    # ここに実装
  end
end

な形とし、 ここに実装 へ実装していくものとします。

 

JavaScriptは fetch() + Railsは redirect_to() の場合

redirect_to のオプションなし

まず、Rails側は

redirect_to after_redirect_erb_fruits_path

とします。

次に、JavaScript側は

const res = await fetch(`/api/try_redirect/fruits/${id}`)

console.log(res)
console.log(res.status)

const t = await res.text()
console.log(t)

とします。

この実装で動作確認したところ、ブラウザの Console にレスポンスのHTTPステータスが 200 と出力されました。

 
また、レスポンスボディの出力も見たところ、リダイレクト先の情報を取得しているように見えました。

 

redirect_to のオプションにステータスコードを指定

次に redirect_toステータスコード 303 (see other) を与えてみます。
ActionController::Redirecting

redirect_to after_redirect_erb_fruits_path, status: :see_other

 
しかし、結果は同じく

でした。

 

リダイレクト先も読みに行っている原因を調査

curlのときと何が違うのかを調べてみたところ、以下の情報がありました。

 
fetch() の場合、リダイレクトモードのデフォルトが follow なため、自動でリダイレクト先のデータも取得してしまったようでした。
WindowOrWorkerGlobalScope.fetch() - Web API | MDN

 

JavaScriptはオプション redirect: manual な fetch() + Railsは redirect_to() の場合

fetch() のリダイレクトモードによる違いがあるか気になったため、リダイレクトモードを変更して試してみます。

fetch() にオプション redirect: manual を渡すよう、JavaScript側を修正します。

const res = await fetch(`/api/try_redirect/fruits/1`,
                    {redirect: 'manual'}) // 追加

 
Railsはそのままの実装として動作確認すると、

  • responseの typeopaqueredirect
  • status が 0
  • レスポンスボディが空
  • リダイレクト先の読み込みしてなさそう

という結果に変わりました。

この結果は Response オブジェクトの type = opaqueredirect に書かれている内容の通りでした。
Response.type - Web APIs | MDN

 

JavaScriptはfetch() + Railsは render() の場合

fetch()redirect オプションを使うことで自動的なリダイレクト先の読み込みがなくなりそうでした。

ただ、

などを行おうとすると、 fetch()redirect オプションでは厳しそうでした。

 

そこで、Railsrender() で 302 を返してみるとどうなるかを見てみます。

JavaScript側は

const res = await fetch(`/api/try_redirect/fruits/${id}`)

とします。

また、Rails側は

となるよう

render status: :found, json: {path: params[:id]}

な実装にします。

 
動作確認をしたところ、

な結果がブラウザの Console に出力されました。

 

JavaScriptはfetch() + Railsは render() + location の場合

先ほどの実装でやりたいことは満たせそうですが、もう少し render() について調べてみます。

render() のリファレンスより location オプションを使うことでHTTPヘッダの Location に値を渡せそうでした。
2.2.13.3 :locationオプション - 2.2.13 renderのオプション | レイアウトとレンダリング - Railsガイド

 
そこで、Rails側を

render status: :found, location: after_redirect_erb_fruits_path, json: {path: params[:id]}

として動作確認したところ、 redirect_to() と同様、リダイレクト先も自動で読み込まれました。

 
ここで redirect_to() の実装を見てみると、 location を設定していました。
https://github.com/rails/rails/blob/24ebaa4e83b8809be5145bc31d68f267daadfe20/actionpack/lib/action_controller/metal/redirecting.rb#L89

そのため、 renderlocation を設定した場合は、 redirect_to と同じ動作になりそうでした。

 

まとめ

fetch() を使う時に、自動でリダイレクト先も読み込まないようにするには

あたりを使うのが良さそうでした。

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/react_with_vite_rails-sample

今回のプルリクはこちらです。
https://github.com/thinkAmi-sandbox/react_with_vite_rails-sample/pull/4