The problem: Turbo just appends the response from server to the end of current page. Not replacing the page like expected.
Recently upgraded from Rails 6 to 7. Also I switched from importmap to esbuild.
I have a form that is making a post to a create method. The controller responds with
if @stamp.save
redirect_to stamps_path, notice: "Input saved"
else
The strange thing that happens is that the response is being appended to the bottom of the source code from the top. So at the end of the original source code a new head-section from the response is appended like
<...> The whole initial html <...>
</body>
<title>Page title</title> <-- The server response gets appended after </body>
<meta .... the whole page gets repeated again
Not really sure what to show you here, been digging through all settings etc. I have never seen Turbo behave like this with a respons.
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.1.2"
gem "rails", "~> 7.0.2", ">= 7.0.2.4"
gem "sprockets-rails"
gem "puma", "~> 5.6"
gem "haml-rails", "~> 2.0"
gem "sqlite3", "~> 1.4"
gem 'devise', '~> 4.8'
gem 'devise-i18n', '~> 1.10'
gem "turbo-rails"
gem "jsbundling-rails", "~> 1.0"
gem 'sass-rails'
gem "stimulus-rails"
gem 'rubyXL', '~> 3.3'
gem "jbuilder"
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
gem 'kaminari', '~> 1' # pagination gem
gem "bootsnap", require: false
group :development, :test do
gem "debug", platforms: %i[ mri mingw x64_mingw ]
end
group :development do
gem "web-console"
end
group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
end
{
"private": true,
"license": "ISC",
"name": "name",
"version": "0.1.0",
"scripts": {
"release": "npm run build && npm run publish",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@hotwired/stimulus": "^3.1.0",
"@hotwired/turbo-rails": "^7.2.5",
"@popperjs/core": "^2.11.5",
"@rails/activestorage": "^6.0.0",
"bootstrap": "^5.2.0",
"esbuild": "^0.17.7"
}
}
I've been trying to replicate another rails app I have using Turbo. Been looking into all settings etc. Other pages can take a redirect_to and replacing the content as expected. I'm using haml so the HTML-structure should be fine.
I can only think of a one way this could happen: you have
show.erb
template. Just rename it toshow.html.erb
(or .haml, .slim, it doesn't matter).By default, the forms are submitted as TURBO_STREAM, it's what rails shows in the logs, however, this is not the whole picture. The form submit request sets
Accept
header:These are the response
Content-Type
s that turbo expects to receive. When you userespond_to
block in controller actions,Accept
header is what determines whichformat
block to call:When you use
respond_to
block, rails will automatically set some render options, one of which is:content_type
which in turn setsContent-Type
response header. So ifformat.html
block runs it setsContent-Type: text/html
which turbo knows how to handle by redirecting or replacing the page content.The important part is that we went from turbo_stream format to html. Needless to say that html needs to render html looking response and turbo_stream has to be a
<turbo-stream>
tag.What happens when you don't use
respond_to
block. Rails will try to render a template that matches the format. For example, turbo_stream request will look forshow.turbo_stream.erb
thenshow.html.erb
thenshow.erb
, like this:Let's say it finds
show.html.erb
, because there is anhtml
extension present, rails will set response Content-Type totext/html
and everything works as expected.However, if it finds
show.erb
, there is nothing to tell rails that it is in html format, we didn't userespond_to
block so response Content-Type is not set explicitly, the only option left is to fallback to the first Accept type, which is turbo_stream. Now you're rendering an html template but response type is turbo_stream, and there is no<turbo-stream>
tag.When turbo sees a turbo_stream response it appends it to the document, which is what you're seeing. Since there are no <turbo-stream> instructions, it just stays untouched.
Long story short, something along the way needs to set the correct content type.
Either use
respond_to
block which will set necessary render options. Or set render options yourself:content_type: "text/html"
. Or let rails know the content type by usinghtml.erb
andturbo_stream.erb
extensions.Note, when you redirect, turbo_stream content type stays, because turbo handles the redirect on the front end. If there is no
html.erb
extension and norespond_to
block in the show action, you get a content type mismatch.