The scenario
We run a busy shop with a product grid. Product pages are slow to render, and each view increments a counter we use to decide what's popular. The pitfall code is nothing special: a grid of link_to cards in _product.html.erb and a show action that calls increment!(:views_count).
The burn
Move the cursor across the grid without clicking, then reload. The view counts have gone up, and the server has rendered a page for every card the cursor lingered on.
Since Turbo 8, Drive prefetches any link the user hovers over for 100 milliseconds. Nobody opted in; it's on for every link by default. When the hover turns into a click, the page appears instantly. But most hovers never become clicks, and each one costs a full server render that gets thrown away. On an app that hasn't already made its pages cheap, that adds up fast.
The second surprise is side effects. Anything a GET request does when a page is "viewed", whether counting it, setting a cookie, or marking something as seen, now also happens for pages nobody looked at. Here that corrupts the popularity numbers, and anything built on them.
The fix
Invert the default. Put data-turbo-prefetch="false" on <body> in the layout, as the comment at the top of the refactored index.html.erb notes, and nothing prefetches. A link's own data-turbo-prefetch="true" overrides an ancestor's false, so we opt back in where intent is high: _product.html.erb takes a prefetch local, and index.html.erb passes true for the "Popular right now" row and false for the rest of the grid.
For the side effect, Turbo marks its prefetch requests with an X-Sec-Purpose: prefetch header. The refactored products_controller.rb checks for it and skips the counter.
Why it matters
Prefetch is a bet, and caching sets the odds. A wasted prefetch of a cached page costs almost nothing; a wasted prefetch of an expensive, uncached render costs a lot. The same feature can be a good deal on one surface and a bad one on another, so decide per surface based on what a wasted request costs you. That's much easier when prefetching is something you turn on, not something you discover.
There's an honest gap in the header check: a click served from the prefetch cache never makes a second request, so a prefetched-then-clicked page isn't counted at all. The complete fix is to record the view from the browser after the page is actually shown.
Read More
- Turbo Handbook: Prefetching links on hover, covering
data-turbo-prefetchand theX-Sec-Purposeheader.