Possibly buggy cache handling when using hx-select / hx-target in htmx 2.x
I have two files "first.html"
<!doctype html>
<html>
<head><title>First</title></head>
<body>
<ul hx-boost="true" hx-select="main" hx-target="main" hx-swap="outerHTML">
<li><a href="first.html">First</a></li>
<li><a href="second.html">Second</a></li>
</ul>
<main> First </main>
<!--script src="https://unpkg.com/[email protected]"></script-->
<script src="https://unpkg.com/[email protected]"></script>
</body>
</html>
and "second.html"
<!doctype html>
<html>
<head><title>Second</title></head>
<body>
<ul hx-boost="true" hx-select="main" hx-target="main" hx-swap="outerHTML">
<li><a href="first.html">First</a></li>
<li><a href="second.html">Second</a></li>
</ul>
<main> Second </main>
<!--script src="https://unpkg.com/[email protected]"></script-->
<script src="https://unpkg.com/[email protected]"></script>
</body>
</html>
served by some (any) webserver from the same directory.
I open http://.../first.html in some browser (I tried Firefox, Chromium and MS Edge (Linux)) and perform the following steps:
| Action | URL in browser after action | content shown after action |
|---|---|---|
| first.html | "First" | |
| Click on link "Second" | second.html | "Second" |
| Browser "back" | first.html | "First" |
| Click on link "Second" | second.html | "Second" |
| Browser "back" | first.html | "Second" <-- WTF? |
After the second time I stepped back through the browser history, URL and content have diverged!
Now if I remove hx-select="main" hx-target="main" hx-swap="outerHTML" and start again (SHIFT+F5 hard reload for "first.html") everything works.
Also, if I keep hx-select="main" hx-target="main" hx-swap="outerHTML" but switch to HTMX 1.9.12 everything works, too.
So to me this really looks like a bug in HTMX 2.x?!
Can anyone confirm if it is a bug or show me what I'm doing wrong?
Thanks! Heiko
yeah I was able to reproduce this and it does look like a bug to me.
checked contents of the htmx-history-cache in local storage and I get this which has second in both history data items so it seems the bug is in the save current page to history feature somehow
[
{
"content": "\n <ul hx-boost=\"true\" hx-select=\"main\" hx-target=\"main\" hx-swap=\"outerHTML\">\n <li><a href=\"first.html\">First</a></li>\n <li><a href=\"second.html\" class=\"\">Second</a></li>\n </ul>\n <main class=\"\"> Second </main>\n <!--script src=\"https://unpkg.com/[email protected]\"></script-->\n <script src=\"https://unpkg.com/[email protected]/dist/htmx.js\"></script>\n \n",
"scroll": 0,
"title": "Second",
"url": "/second.html"
},
{
"content": "\n <ul hx-boost=\"true\" hx-select=\"main\" hx-target=\"main\" hx-swap=\"outerHTML\">\n <li><a href=\"first.html\">First</a></li>\n <li><a href=\"second.html\" class=\"\">Second</a></li>\n </ul>\n <main class=\"\"> Second </main>\n <!--script src=\"https://unpkg.com/[email protected]\"></script-->\n <script src=\"https://unpkg.com/[email protected]/dist/htmx.js\"></script>\n \n",
"scroll": 0,
"title": "Second",
"url": "/first.html"
}
]
Ok found your issue. similar issues were reported in #1076
The cause of your issue is that you have placed the htmx running javascript code inside the body of your page that is getting replaced with every boosted navigation and history restore. This is causing the internal global variable currentPathForHistory to get into a confused state with the wrong value somehow. Moving the htmx script to the page head and then not replacing it every time resolves the issue.
Wow, that was quick.
The cause of your issue is that you have placed the htmx running javascript code inside the body of your page that is getting replaced with every boosted navigation and history restore.
Hm, but the problem occurred while using hx-select="main" hx-target="main" - and that the <script>-Tag is outside of main so shouldn't get replaced?
Anyway, I'll give this a try in the simplified example code and also in the real application and will report back if the problem is solved there, too.
Could hx-history-elt be an alternative to putting the script in head maybe? As by default, the body element is the one used and restored through history navigation
Being fairly new to HTMX I had so far overlooked hx-history-elt but I'll give that a try, too.
Thanks for the hint!
Yeah sorry it is not the boosted swapping that seems to be swapping out the body as you are only targeting main as you said. But history is replacing body when you go back which seems to be launching a new copy of htmx js script. The dev tools even starts showing the htmx.js file running with a VM1234 style prefix which I think means the htmx javascript is executing in a old disconnected and separated vm instance now. The state between the two instances of htmx.js seem different and it causing the problems for the currentPathForHistory variable which is how htmx tracks the current url. Anyway moving the script to recommended places like the head seems the best solution.
Also did a quick test locally with replacing the global variable currentPathForHistory
currentPathForHistory = path
with
localStorage.setItem('htmx-current-path-for-history', path)
to move it to localstorage plus some checks for when localstorage is disabled Confirmed this works well and resolves the issue but not sure if this is worth making into a PR.
Edit: actually this should be in sessionStorage so multiple tabs work as expected.
OK, I can confirm that everything works fine if the <script> element is added to the <head> instead of the <body>.
However, since I could not reproduce the problem when using HTMX 1.9.12 it looks like a regression which still might warrant a fix in 2.x.
Otherwise I would suggest to at least add a more prominent warning to the docs not to load HTMX from a <script> element within the <body>. Placing those script elements just before the closing <body> tag is something at least I am quite used to.
Unfortunately I did not have time yet for experimenting with hx-history-elt so cannot comment on the effects it might have here.
Heiko
Yeah current docs (and v1) for installing has:
Installing
Htmx is a dependency-free, browser-oriented javascript library. This means that using it is as simple as adding a <script> tag to your document head. There is no need for a build system to use it.
Via A CDN (e.g. unpkg.com)
The fastest way to get going with htmx is to load it via a CDN. You can simply add this to your head tag and get going:
But that is about all it has on the subject. Not sure what is the best way to add to this.
I think v1 had different history implementation and they improved a few things which has lead to this change. Even in v1 it would have had the same issue reloading and running the whole htmx script state with every history/boost which could cause issues if you override the htmx config items values in my testing as these can all get reset.