Environment
- @hotwired/turbo: 8.0.23 (via turbo-rails 2.0.23)
- Browser: Chrome, Safari (all tested)
Description
When a <turbo-frame> with data-turbo-action="advance" navigates and the server responds with a frame-only layout (not a full HTML page), view.forceReloaded is permanently set to true and never reset. This causes performScroll() to skip scrollToTop() on all subsequent Visits for the lifetime of the page.
Steps to reproduce
- Create a
<turbo-frame id="main" data-turbo-action="advance"> in your layout
- Use a frame-only layout for
Turbo-Frame: main requests (standard optimization to reduce response size)
- Click a link with
data-turbo-frame="main" to navigate the frame (URL advances)
- Now click any link with
data-turbo-frame="_top" that should trigger a full Visit
- Expected: page scrolls to top
- Actual: scroll position is preserved (stuck at previous position)
Root cause
In renderPage():
renderPage(e, t = false, s = true, r) {
const i = new RendererClass(this.snapshot, e, t, s);
return i.shouldRender ? r?.changeHistory() : this.forceReloaded = true, this.render(i);
}
When the frame response is not a full HTML page, shouldRender returns false, so forceReloaded is set to true.
Then in performScroll():
performScroll() {
this.scrolled || this.view.forceReloaded || this.view.shouldPreserveScrollPosition(this) || (
"restore" == this.action
? this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop()
: this.scrollToAnchor() || this.view.scrollToTop(),
this.scrolled = true
)
}
forceReloaded is true, so the entire scroll logic is skipped. Since forceReloaded is never reset to false, every subsequent Visit skips scroll reset.
Expected behavior
forceReloaded should be reset to false before each new Visit, e.g. at the start of Visit.start() or when a new Visit is constructed.
Current workaround
document.addEventListener("turbo:before-visit", () => {
Turbo.navigator.view.forceReloaded = false
})
Environment
Description
When a
<turbo-frame>withdata-turbo-action="advance"navigates and the server responds with a frame-only layout (not a full HTML page),view.forceReloadedis permanently set totrueand never reset. This causesperformScroll()to skipscrollToTop()on all subsequent Visits for the lifetime of the page.Steps to reproduce
<turbo-frame id="main" data-turbo-action="advance">in your layoutTurbo-Frame: mainrequests (standard optimization to reduce response size)data-turbo-frame="main"to navigate the frame (URL advances)data-turbo-frame="_top"that should trigger a full VisitRoot cause
In
renderPage():When the frame response is not a full HTML page,
shouldRenderreturnsfalse, soforceReloadedis set totrue.Then in
performScroll():forceReloadedistrue, so the entire scroll logic is skipped. SinceforceReloadedis never reset tofalse, every subsequent Visit skips scroll reset.Expected behavior
forceReloadedshould be reset tofalsebefore each new Visit, e.g. at the start ofVisit.start()or when a new Visit is constructed.Current workaround