When Unpoly updates a page's main fragment, it also updates the browser's address bar,
the window title and the meta tags in the <head>. The result looks and behaves like a full page load:
the user can bookmark the URL, share it, or return to it with the Back button.
Updates to minor fragments leave history untouched.
Following a link or submitting a form counts as navigation. When navigation updates the layer's main fragment, Unpoly adds a history entry for the new URL:
<a href="/posts/5" up-follow>Read post</a> <!-- mark: up-follow -->
Clicking the link fetches /posts/5 and swaps the main fragment.
The address bar now shows /posts/5, and the window title is taken from the response's <title>.
A link that updates a smaller fragment does not change history:
<a href="/comments?page=2" up-target="#comments">Next page</a> <!-- mark: up-target="#comments" -->
Clicking this link replaces only the #comments element. The address bar keeps its URL,
so the user does not collect a history entry for every minor update.
This is the auto setting of the [up-history] attribute, which is the default for links and forms.
History is updated when the targeted fragment contains an element matching up.fragment.config.autoHistoryTargets.
By default that is the layer's main fragment.
When the new URL is the one already in the address bar, no entry is added.
Note
An overlay only shows its URL in the address bar when it has visible history. This page describes the root layer, which always has visible history.
To update history even when a minor fragment is updated, set [up-history=true]:
<a href="/comments?page=2" up-target="#comments" up-history="true">Next page</a> <!-- mark: up-history="true" -->
To never update history, set [up-history=false]:
<a href="/posts/5" up-follow up-history="false">Peek at post</a> <!-- mark: up-history="false" -->
Forms accept the same [up-history] attribute.
When rendering from JavaScript, pass a { history } option instead.
To change the default for all links and forms, configure up.fragment.config.navigateOptions.history.
To decide per link, listen to up:link:follow or up:form:submit and set event.renderOptions.history:
up.on('up:link:follow', function(event, link) {
if (link.matches('.pagination a')) {
event.renderOptions.history = false // mark-line
}
})
To stop Unpoly from ever changing history, or only some aspects of it, see Turning off history aspects.
If your app has a significant element that is not the main fragment,
you can add its selector to up.fragment.config.autoHistoryTargets:
up.fragment.config.autoHistoryTargets.push('.content')
Now a link targeting .content, or any fragment that contains a .content element,
updates history without an explicit [up-history=true].
GET requests update historyOnly a GET request can be reloaded or restored by the browser, so only a GET response updates history.
A response to a POST, PUT or PATCH request never changes history, even with [up-history=true].
When a successful submission redirects to a GET URL, the redirect's response updates history
with the new URL. Unpoly detects the redirect from the response URL, or from an X-Up-Method header
that your server can set.
The exception is an explicit location: when you set [up-location]
or pass a { location } option, Unpoly pushes that URL even after a non-GET request.
The same rules apply to error responses. When an error response to a GET request renders an error page into the
main fragment, the address bar shows its URL, like after a full page load. A form submission that fails
with a validation error does not change history. See Handling error responses.
The low-level up.render() function never updates history by default:
up.render({ target: '.content', url: '/path' }) // History is unchanged
Pass a { history } option to opt in:
up.render({ target: '.content', url: '/path', history: true }) // Always update
up.render({ target: '.content', url: '/path', history: 'auto' }) // Update if the main fragment is rendered
To render with all navigation defaults, including { history: 'auto' },
use up.navigate() instead of up.render().
A history update comprises the following:
meta[name=description] or link[rel=canonical].[lang] attribute of the root <html> element.<head>.In the document below, the highlighted nodes are updated when history changes, in addition to the location URL:
<html lang="en"> <!-- mark: lang="en" -->
<head>
<title>Our team · AcmeCorp</title> <!-- mark-line -->
<link rel="canonical" href="https://example.com/team"> <!-- mark-line -->
<meta name="description" content="About the AcmeCorp team"> <!-- mark-line -->
<meta property="og:image" content="https://example.com/team.jpg"> <!-- mark-line -->
<script src="/assets/app.js"></script>
<link rel="stylesheet" href="/assets/app.css">
<script type="application/ld+json">...</script> <!-- mark-line -->
</head>
<body>
...
</body>
</html>
The linked JavaScript and stylesheet are not part of history state and will not be updated. See Reacting to new deployments for strategies to detect new app deployments.
To update an additional <head> element during history changes, mark it with an [up-meta] attribute:
<link rel="license" href="https://opensource.org/license/mit/" up-meta> <!-- mark: up-meta -->
To keep an element that would be updated by default, set [up-meta=false]:
<meta name="theme-color" content="#ffffff" up-meta="false"> <!-- mark: up-meta="false" -->
To change the defaults for all pages, configure up.history.config.metaTagSelectors
and up.history.config.noMetaTagSelectors. Only elements in the <head> are ever considered.
You can keep aspects of the history state unchanged while the rest is updated. Each aspect has an attribute for links and forms, and a render option for JavaScript:
| Keep unchanged | Attribute | Render option |
|---|---|---|
| URL | [up-location=false] |
{ location: false } |
| Title | [up-title=false] |
{ title: false } |
| Meta tags | [up-meta-tags=false] |
{ metaTags: false } |
html[lang] |
[up-lang=false] |
{ lang: false } |
For example, this link updates the URL and title, but leaves the meta tags alone:
<a href="/posts/5" up-follow up-meta-tags="false">Read post</a> <!-- mark: up-meta-tags="false" -->
These options only take effect when a render pass updates history in the first place.
To keep an aspect unchanged for all render passes, see Turning off history aspects.
Instead of the response's URL and <title>, you can set your own values.
Give [up-location] or [up-title] a string instead of false:
<a href="/posts/5?utm=newsletter" up-follow up-location="/posts/5" up-title="My post">Read post</a> <!-- mark: up-location="/posts/5" up-title="My post" -->
The server can do the same by responding with an X-Up-Location or X-Up-Title header.
This is useful when you optimize a response to omit the layout,
and the response no longer includes a <title>.
An explicit value cannot update an aspect that is turned off in the configuration.
To stop Unpoly from ever changing history, set up.history.config.enabled = false.
Fragments are still updated, but the address bar, window title, meta tags and html[lang] stay as they are.
To turn off only some aspects, set an array of the aspects that Unpoly may update.
The aspects are 'location', 'title', 'metaTags' and 'lang'.
For example, an app that keeps URLs out of the address bar and browser history for privacy
can still let html[lang] follow the content:
up.history.config.enabled = ['lang'] // mark: ['lang']
Attributes and render options can keep more aspects unchanged for a single render pass, but they can never update an aspect that the configuration excludes.
Turning off the 'location' aspect has some consequences:
up.history.push() and up.history.replace() do nothing.#hash still scroll to the matching fragment, but don't change the URL.For every aspect that is turned off, a layer reports what it rendered, not what the browser shows.
For example, up.layer.location is the URL of the rendered content,
so an [up-nav] bar highlights links to the content instead of the unchanged address bar.
Closing an overlay leaves aspects that are turned off alone,
so a title that your own code set while the overlay was open is kept.
To add a history entry without rendering, call up.history.push():
up.history.push('/posts/5')
To change the URL of the current entry, call up.history.replace().
Neither function updates the title or meta tags.
Entries placed this way are owned by Unpoly. When the user goes back to such an entry,
Unpoly restores the content at that URL.
To push an entry that your own script will restore, use the browser's history.pushState() instead.
After the address bar changed for any reason, Unpoly emits an up:location:changed event:
up.on('up:location:changed', function(event) {
console.log('New location is', event.location)
})
The event has a { reason } property that tells whether an entry was pushed or replaced,
whether the user went back, or whether only the #hash changed.
See Tracking page views for using this event to track page views.
Tip Your coding agent can search these docs locally with the Unpoly agent skill