htmx icon indicating copy to clipboard operation
htmx copied to clipboard

http-equiv="refresh" and hx-boost don't work together (well)

Open dashorst opened this issue 9 months ago • 5 comments

In an application I have a top level menu that is hx-boosted, and have in the <head> section a refresh for every 3 minutes:

<head>
    <meta http-equiv="refresh" content="180">

When I navigate to a different top menu item, it is nicely hx-boosted, and even the URL in the browser bar reflects the new URI of the page. However when the 3 minutes are up, the browser requests the original URL instead of the updated URL in the browser's URL field.

If I request the URL of the menu item manually the page correctly stays on that URI.

dashorst avatar Feb 26 '25 11:02 dashorst

I can't think of an easy way to fix this in htmx as this is browser built in behavior causing this and would probably need a major change to the browsers to support this use case. I think all web frameworks and libraries that manipulate the URL without performing old school refreshes (e.g. All SPA frameworks) will be impacted by this issue.

Note the purpose of hx-boost is to avoid refreshing the whole page when it is not needed so it is kind of at odds with this browser built in feature.

However it should be fairly easy to replicate this browser feature in a htmx website. htmx allows much much more control and you can place a hx-trigger with "every 180s" on any element you want on the page and get it to fetch and update whatever you want from your server and place it however or wherever you want. you should be able to put this on a hx-get="" which i think will fetch the current url for example. But reloading the whole page every time like this may not be really what you want and you may only need to replace a partial area of your page like for example the latest news feed div on the right with latest content every 180 seconds. Also it is possible to do advanced things like do the get every 180 seconds but detect on the server if no new updates send back nothing using hx-swap="none" and if there are updates detected instead return them in a hx-swap-oob or return the hx-reswap header to override the none swap when required. Another handy thing to know is that if you place a id on the element that does the polling every 180 seconds then the request will include this id in the HX-Trigger request header so your backend server knows it is a polling request to the url and not a new page navigation so you can use this to help decide what optional response to return

MichaelWest22 avatar Feb 27 '25 13:02 MichaelWest22

<div hx-trigger="every 180s" hx-get="" hx-target="body"></div>

You could try adding this to a page to see if this works the same as the refresh browser head method.

MichaelWest22 avatar Feb 28 '25 01:02 MichaelWest22

If you want a hard refresh, then a JS solution seems fine and it can fall back to the meta tag (if JS isn't available then htmx wouldn't work anyway). Set the JS refresh trigger to be just before the meta tag one:

<meta http-equiv="refresh" content="180">
<script>setTimeout(() => window.location.reload(true), (180 - 1) * 1000)</script>

scrhartley avatar Feb 28 '25 13:02 scrhartley

Otherwise:

<meta http-equiv="refresh" content="180">
<script>
    document.addEventListener('htmx:pushedIntoHistory', function() {
        const meta = document.head.querySelector('meta[http-equiv=refresh]')
        meta.replaceWith(meta.cloneNode())
    })
</script>

scrhartley avatar Feb 28 '25 22:02 scrhartley

The last solution of @scrhartley seems like the best way if I was to keep the boost and the hard refresh. I've removed the boost from the navigation element which fixed the issue for the users.

Thanks for looking into this!

dashorst avatar Mar 03 '25 09:03 dashorst