Reacting to new deployments
After you deploy a new version of your app, users with an open page keep running the old JavaScript and CSS, since Unpoly updates fragments instead of loading new pages. Unpoly detects new asset versions in server responses and emits an event, so you can prompt the user to reload, reload at the next opportunity, or load the new files.
Detecting a new version
Unpoly tracks your application's assets: by default every script or stylesheet with a remote source in the <head>.
In the document below, the highlighted elements are assets:
<html>
<head>
<title>Our team · AcmeCorp</title>
<link rel="stylesheet" href="/assets/frontend-5f3aa101.css"> <!-- mark-line -->
<script src="/assets/frontend-81ba23a9.js"></script> <!-- mark-line -->
<script>console.log('loaded!')</script>
<link rel="canonical" href="https://example.com/team">
</head>
<body>
...
</body>
</html>
Whenever Unpoly renders a response with a <head>, it compares the assets on the current page
with the assets in the response. After a deployment, the fingerprinted file names differ,
and Unpoly emits an up:assets:changed event on the document:
up.on('up:assets:changed', function(event) {
console.log('A new version was deployed')
})
Responses without a <head>, like shortened responses, are not compared.
If your server shortens every response, use the polling detector below.
There is no default behavior when assets have changed. In particular, Unpoly does not insert the new asset elements into the current page. The sections below show popular ways to react.
To include elements like inline scripts or <meta> tags, mark them with an [up-asset] attribute.
To exclude an asset, set [up-asset="false"]. See [up-asset] for details.
Notifying the user
A friendly way to handle a new version is a notification banner, offering to reload the page. The user can reload at their convenience, without losing their work:
A banner reads "A newer version is available!" next to a Reload button.
The code below inserts a clickable #new-version banner when assets change:
up.on('up:assets:changed', function() {
// If we are already showing a notification, do nothing.
if (document.querySelector('#new-version')) return
// Append a <button id="new-version"> notification to the <body>
up.element.affix(document.body, 'button#new-version', {
text: 'A new app version is available. Click to reload.',
onclick: 'location.reload()',
})
})
Tip
The code uses
up.element.affix()to quickly create a DOM element from a CSS selector.
Reloading at the next navigation
An invisible way to handle a new version is to make a full page load when the user follows the next link. This unloads all scripts and stylesheets and boots the app from scratch:
let assetsChanged = false
up.on('up:assets:changed', function() {
assetsChanged = true
})
up.on('up:link:follow', function(event) {
if (assetsChanged && isLoadPageSafe(event.renderOptions)) {
// Prevent the render pass
event.preventDefault()
// Make a full page load without Unpoly
up.network.loadPage(event.renderOptions)
}
})
function isLoadPageSafe({ url, layer, method }) {
// Default to 'GET' and uppercase the method string
let isSafeRequest = url && up.util.normalizeMethod(method) === 'GET'
// To prevent any overlays from closing, we only make a full page load
// when the link is changing the root layer.
let isRootLayer = up.layer.current.isRoot() && layer !== 'new'
return isSafeRequest && isRootLayer
}
Loading new assets
The up:assets:changed event has { oldAssets, newAssets } properties
that you can use to insert the new assets yourself.
The code below replaces all <link rel="stylesheet"> elements whenever they change:
function isStylesheet(asset) {
return asset.matches('link[rel=stylesheet]')
}
up.on('up:assets:changed', function({ oldAssets, newAssets }) {
let oldStylesheets = up.util.filter(oldAssets, isStylesheet)
for (let oldStylesheet of oldStylesheets) {
oldStylesheet.remove()
}
let newStylesheets = up.util.filter(newAssets, isStylesheet)
for (let newStylesheet of newStylesheets) {
document.head.append(newStylesheet)
}
})
Scripts cannot be swapped in the same way. Removing a <script> element does not unload
its code, so a new script version would run next to the old one.
Handle script changes with one of the other techniques on this page.
Detecting a new version without user interaction
Unpoly only compares assets when it renders a response. To detect a deployment while the user is idle, poll an empty fragment every few minutes:
<div id="version-detector" up-poll up-interval="120_000" up-source="/version"></div>
This reloads the #version-detector element from /version every two minutes.
The /version route must render a full HTML page with your assets in the <head>
and an empty <div id="version-detector"> in the <body>.
Detecting a new backend version
A deployment may only change backend code, leaving the frontend assets untouched.
To detect such a deployment, render the deployed commit hash in a <meta> tag
and mark it as an asset with [up-asset]:
<meta name="backend-version" content="d50c6dd629e9bbc80304e14a6ba99a18c32ba738" up-asset> <!-- mark: up-asset -->
When the hash changes, up:assets:changed is emitted like for any other asset.
Aborting the render pass
The up:assets:changed event is emitted after the response was loaded, but before any fragment is changed
and before the browser history is updated. If you cannot allow rendering with changed assets,
prevent the event and make a full page load instead:
up.on('up:assets:changed', function(event) {
event.preventDefault() // mark: event.preventDefault()
up.network.loadPage(event.renderOptions)
})
The render pass is aborted, and the browser loads the new page with the new assets instead.