Search code examples
ruby-on-railshotwire-railsturbo

Links inside turbo streams is making requests twice in Rails


I have 3 models and each model has an index page that loads all its respective instances lazily with the link to its show page. Imagine something like this:

enter image description here

On clicking the link of any instance of any model, it makes a couple of requests for the same page & request type:

22:32:33 web.1  | Started GET "/transactions/5709759" for 127.0.0.1 at 2023-11-23 22:32:33 +0530
22:32:33 web.1  | Processing by TransactionsController#show as HTML
22:32:33 web.1  |   Parameters: {"id"=>"5709759"}
22:32:33 web.1  |   Transaction Load (0.4ms)  SELECT "transactions".* FROM "transactions" WHERE "transactions"."slug" = $1 ORDER BY "transactions"."date" DESC LIMIT $2  [["slug", "5709759"], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ config/initializers/cancancan.rb:18:in `find'
22:32:33 web.1  |   User Load (0.4ms)  SELECT "users"."id", "users"."slug" FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/application_controller.rb:19:in `current_user'
22:32:33 web.1  |   Thaali Load (1.0ms)  SELECT "thaalis".* FROM "thaalis" WHERE "thaalis"."id" = $1 LIMIT $2  [["id", 168], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/transactions_controller.rb:18:in `show'
22:32:33 web.1  |   Sabeel Load (0.5ms)  SELECT "sabeels".* FROM "sabeels" WHERE "sabeels"."id" = $1 LIMIT $2  [["id", 69], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/transactions_controller.rb:19:in `show'
22:32:33 web.1  |   Rendering layout /usr/share/rvm/gems/ruby-3.2.2/gems/turbo-rails-1.5.0/app/views/layouts/turbo_rails/frame.html.erb
22:32:33 web.1  |   Rendering transactions/show.html.erb within layouts/turbo_rails/frame
22:32:33 web.1  |   Rendered transactions/_transaction.html.erb (Duration: 0.5ms | Allocations: 143)
22:32:33 web.1  |   Rendered shared/_destroy_modal.html.erb (Duration: 0.5ms | Allocations: 126)
22:32:33 web.1  |   Rendered shared/_actions.html.erb (Duration: 1.2ms | Allocations: 243)
22:32:33 web.1  |   Rendered transactions/show.html.erb within layouts/turbo_rails/frame (Duration: 2.7ms | Allocations: 580)
22:32:33 web.1  |   Rendered layout /usr/share/rvm/gems/ruby-3.2.2/gems/turbo-rails-1.5.0/app/views/layouts/turbo_rails/frame.html.erb (Duration: 3.0ms | Allocations: 679)
22:32:33 web.1  | Completed 200 OK in 34ms (Views: 3.9ms | ActiveRecord: 2.3ms | Allocations: 3942)
22:32:33 web.1  | 
22:32:33 web.1  | 
22:32:33 web.1  | Started GET "/transactions/5709759" for 127.0.0.1 at 2023-11-23 22:32:33 +0530
22:32:33 web.1  | Processing by TransactionsController#show as HTML
22:32:33 web.1  |   Parameters: {"id"=>"5709759"}
22:32:33 web.1  |   Transaction Load (0.4ms)  SELECT "transactions".* FROM "transactions" WHERE "transactions"."slug" = $1 ORDER BY "transactions"."date" DESC LIMIT $2  [["slug", "5709759"], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ config/initializers/cancancan.rb:18:in `find'
22:32:33 web.1  |   User Load (0.2ms)  SELECT "users"."id", "users"."slug" FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/application_controller.rb:19:in `current_user'
22:32:33 web.1  |   Thaali Load (0.7ms)  SELECT "thaalis".* FROM "thaalis" WHERE "thaalis"."id" = $1 LIMIT $2  [["id", 168], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/transactions_controller.rb:18:in `show'
22:32:33 web.1  |   Sabeel Load (0.7ms)  SELECT "sabeels".* FROM "sabeels" WHERE "sabeels"."id" = $1 LIMIT $2  [["id", 69], ["LIMIT", 1]]
22:32:33 web.1  |   ↳ app/controllers/transactions_controller.rb:19:in `show'
22:32:33 web.1  |   Rendering layout layouts/application.html.erb
22:32:33 web.1  |   Rendering transactions/show.html.erb within layouts/application
22:32:33 web.1  |   Rendered transactions/_transaction.html.erb (Duration: 0.4ms | Allocations: 143)
22:32:33 web.1  |   Rendered shared/_destroy_modal.html.erb (Duration: 0.4ms | Allocations: 126)
22:32:33 web.1  |   Rendered shared/_actions.html.erb (Duration: 0.7ms | Allocations: 243)
22:32:33 web.1  |   Rendered transactions/show.html.erb within layouts/application (Duration: 1.6ms | Allocations: 580)
22:32:33 web.1  |   Rendered shared/navbar/_resources_dropdown.html.erb (Duration: 0.1ms | Allocations: 66)
22:32:33 web.1  |   Rendered shared/navbar/_statistics_dropdown.html.erb (Duration: 0.1ms | Allocations: 48)
22:32:33 web.1  |   Rendered shared/navbar/_admin_dropdown.html.erb (Duration: 0.2ms | Allocations: 113)
22:32:33 web.1  |   Rendered shared/navbar/_navbar.html.erb (Duration: 0.8ms | Allocations: 557)
22:32:33 web.1  |   Rendered shared/_flash_messages.html.erb (Duration: 0.0ms | Allocations: 19)
22:32:33 web.1  |   Rendered layout layouts/application.html.erb (Duration: 2.9ms | Allocations: 1529)
22:32:33 web.1  | Completed 200 OK in 17ms (Views: 3.3ms | ActiveRecord: 1.9ms | Allocations: 4808)

If I am correct, it should make a single HTML request and I noticed that the first request has rendered a layout from here:

Rendered layout /usr/share/rvm/gems/ruby-3.2.2/gems/turbo-rails-1.5.0/app/views/layouts/turbo_rails/frame.html.erb (Duration: 3.0ms | Allocations: 679)

while the second request has been rendered from here:

Rendered layout layouts/application.html.erb (Duration: 2.9ms | Allocations: 1529)

This content is rendered from a partial _transactions.html.erb to all.turbo_stream.erb page while the show.html.erb file also renders it making a single HTML request w/o turbo stream.

So my question is, is it normal for the links in the turbo streams to make 2 requests to render a page? Has anyone experienced this issue?

FYI

  • This bug isn't specific to an environment as I have also seen this in my production logs.
  • This bug is also not specific to the turbo-rails version as it had the same bug in the previous version 1.3.2.

Current Specs

  • ruby 3.2.2
  • rails 7.1.2
  • turbo-rails 1.5.0

I am not much experienced in the area of hotwire or turbo so I am not sure what to try and not. So if anyone has experience in this area, I would love your 2 cents on this! :)

Thanks!


Solution

  • If you look in your js console in your browser, you will most likely see something like "frame missing in response, doing full page load".

    The links automatically target the "closest" turbo frame. Then Turbo expects a corresponding Turbo-Frame tag in the response, which is not there. In consequence, Turbo reloads the site with the entire layout (as in a Turbo-Drive request - what you originally intended).

    To solve this, add data-turbo-frame="_top" to your links.

    See also: https://turbo.hotwired.dev/handbook/frames#targeting-navigation-into-or-out-of-a-frame