HTTP redirects in a Turbo-Rails app

posted by Ayush Newatia
15 September, 2022



This post was extracted and adapted from The Rails and Hotwire Codex.

In Rails, a conventional redirect in a controller action looks like:

def create
  # ...

  redirect_to root_path
end

This sends a response with an HTTP status code of 302 Found.

The Turbo docs, however, state that the response to a form submission must be 303 See Other unless something goes wrong.

The reason behind this is the spec for 302 Found states that the redirected request should use the same HTTP method as the original request. Due to legacy reasons, the fetch (used by Turbo under the hood) implementation in most browsers will respond to a 302 Found response from a POST request by issuing a GET request to the redirected path. For all other HTTP methods, it will use the same method as the original request when redirecting.

Responding with a 303 See Other status ensures that the method used for the redirect is always GET.

def create
  # ...

  redirect_to root_path, status: :see_other
end

This anomaly with redirect codes isn’t a massive problem in Rails because Rails forms use the only two browser native methods: GET and POST. Forms with different methods are rendered to use POST with the method inserted as a hidden field which is then parsed by Rails before the request hits a controller.

For example:

<%= form_with(url: posts_path, method: :put) do |form| %>
  <%= form.text_field :name %>

  <%= form.submit %>
<% end %>

will render

<form action="/posts" accept-charset="UTF-8" method="post">
  <input type="hidden" name="_method" value="put">
  <input type="hidden" name="authenticity_token" value="...">

  <input type="text" name="name" id="name">

  <input type="submit" name="commit" value="Save">
</form>

However, this could potentially change as Rails integrates more tightly with Turbo. There are already a couple of use cases where 302 redirects can be problematic.

Turbo submits the form using the method specified in the formmethodattribute when it’s defined. The below form would submit using a PUTrequest.

<%= form_with(url: posts_path) do |form| %>
  <%= form.text_field :name %>

  <%= form.submit formmethod: :put %>
<% end %>

If your controller redirected using a 302 Found, that would result in another PUT request to the redirected path.

Since Turbo intercepts form submissions, you can use any HTTP method in a <form>’s method attribute, not just GET and POST. This form will also submit with a PUT:

<form action="/posts" accept-charset="UTF-8" method="put">
  <input type="text" name="name" id="name">

  <input type="submit" value="Save">
</form>

Rails form helpers won’t render this, but it’s good to know what’s possible!

As such, I believe it’s good practice to ALWAYS redirect with a 303 See Other status code when using Turbo in a Rails app.

If you liked this post, check out my book, The Rails and Hotwire Codex, to level-up your Rails and Hotwire skills!