Unpoly caches responses so that pages the user has already visited this session render instantly on their next visit. Cached content is revalidated with the server after rendering, so stale content is quickly replaced. Cached pages also remain accessible after a disconnect.
Caching is controlled by the { cache } render option or an [up-cache] attribute.
When navigating, the { cache: 'auto' } option is already set by default.
It caches all responses to GET requests, so following a link or submitting a GET form fills the cache without any setup.
A render pass that does not navigate, like a plain up.render() call, only caches when you ask for it:
up.render({ target: '.content', url: '/stories/5', cache: 'auto' }) // mark: cache: 'auto'
Which requests 'auto' caches is decided by up.network.config.autoCache.
By default it caches requests with a safe HTTP method like GET.
You can configure this default.
To force caching regardless of HTTP method, pass { cache: true } or set [up-cache="true"].
Preloading a link also writes its response to the cache.
Error responses are not cached. They also evict an earlier cached response for the same URL. When a request for the same URL is already loading, a second request waits for it instead of making another network request.
Cache entries are only considered fresh for 15 seconds. When Unpoly renders older content from the cache, it reloads the fragment from the server right after. This process is called cache revalidation.
When re-visiting pages, Unpoly often renders twice:
The user sees the cached version instantly and the fresh version a moment later. This has some benefits:
Note
Revalidation only happens after expired content was rendered into the page. No revalidation occurs when expired cache entries are accessed without rendering, e.g. when preloading a cached URL.
Your server-side app is not required to re-render a page when the cached content is still current.
By supporting conditional requests, it can answer a revalidation request with an empty
304 Not Modified response. Unpoly then keeps the fragment it has already rendered.
Revalidation is controlled by the { revalidate } render option or an [up-revalidate] attribute.
When navigating, { revalidate: 'auto' } is the default. It revalidates only expired cache entries.
You can configure both the expiry age and the rule:
up.network.config.cacheExpireAge = 20_000 // expire after 20 seconds
let defaultAutoRevalidate = up.fragment.config.autoRevalidate
up.fragment.config.autoRevalidate = function(response) {
return defaultAutoRevalidate(response) && response.url !== '/dashboard'
}
To revalidate regardless of a cache entry's age, pass { revalidate: true }.
When not navigating, pass { revalidate: 'auto' } or set an [up-revalidate="auto"] attribute to revalidate expired entries.
Navigation is the only moment when Unpoly revalidates by default. To disable revalidation while navigating:
up.fragment.config.navigateOptions.revalidate = false
To keep the default, but disable revalidation for an individual link, set an [up-revalidate="false"] attribute:
<a href="/" up-revalidate="false">Start page</a> <!-- mark: up-revalidate="false" -->
To keep the default, but disable revalidation for a function call that would otherwise navigate,
pass a { revalidate: false } option:
up.follow(link, { cache: true, revalidate: false })
Cached content expires after 15 seconds. This can be configured in up.network.config.cacheExpireAge.
The configured age should at least cover the typical time between preloading a link and following it.
Expired content stays in the cache, but triggers revalidation when rendered. Expired pages also remain accessible after a connection loss.
GET requests don't expire any content. When the user makes a non-GET request (usually a form submission with POST),
the entire cache is expired as soon as the request is sent. A non-GET request has probably changed data on the server,
so all cache entries should be revalidated before they are shown again.
To change what a request expires, use any of the following. A value of false expires nothing:
up.network.config.expireCache
{ expireCache } option to the rendering function[up-expire-cache] attribute on a link or formTo expire additional entries later:
X-Up-Expire-Cache response header from the server.
It cannot undo the expiration that happened when the request was sent.up.cache.expire()
Each of these accepts a URL pattern to expire only matching entries:
up.cache.expire('/users/*')
You can disable the caching mechanism globally or selectively.
Without caching, every request and render pass hits the network. Responses are not cached either, unless there is an existing cache entry that can be updated with a fresher response.
To turn off caching entirely, e.g. while debugging, disable the cache:
up.network.config.cacheEnabled = false
No response is read from or written to the cache. Preloading is disabled as well.
To keep the navigation default, but disable auto-caching for some URLs, configure up.network.config.autoCache:
let defaultAutoCache = up.network.config.autoCache
up.network.config.autoCache = function(request) {
return defaultAutoCache(request) && !request.url.endsWith('/edit')
}
To keep the default, but disable caching for an individual link, set an [up-cache="false"] attribute:
<a href="/stock-charts" up-cache="false">View latest prices</a> <!-- mark: up-cache="false" -->
To keep the default, but disable caching for a function call that would otherwise navigate,
pass a { cache: false } option:
up.follow(link, { cache: false })
Instead of expiring content you may also evict content to erase it from the cache.
In practice you will often prefer expiration over eviction. Expired content remains available during a connection loss and for instant navigation, while revalidation ensures the user always ends up with a fresh revision. Evicted content is gone from the cache entirely, and a new network request is needed to access it again.
Eviction is the right choice when it is not acceptable for the user to see even a brief flash of stale content before revalidation finishes. By default no request evicts content. The controls mirror those for expiration:
up.network.config.evictCache
{ evictCache } option to the rendering function[up-evict-cache] attribute on a link or formX-Up-Evict-Cache response header from the serverup.cache.evict()
Each of these accepts a URL pattern to evict only matching entries. The configuration, option and attribute take effect when the request is sent. The response header takes effect when the response is received.
To limit the memory required to hold its cache, Unpoly evicts cached content in two ways:
up.network.config.cacheEvictAge.up.network.config.cacheSize.To discard revalidated HTML after the server has responded, you may call event.skip()
on an up:fragment:loaded event with a { revalidating: true } property.
This gives you a chance to inspect the response or DOM state right before a fragment would be inserted:
up.on('up:fragment:loaded', function(event) {
// Don't insert fresh content if the user has started a video
// after the expired content was rendered.
let video = event.request.fragment.querySelector('video')
if (event.revalidating && video && !video.paused) { // mark: event.revalidating
// Finish the render pass with no changes.
event.skip()
}
})
See skipping a loaded response for more details and examples.
Compilers with side effects may want to behave differently when the compiled element is being reloaded for the purpose of cache revalidation.
To detect revalidation, compilers may accept a third argument with information about the current render pass. In the example below a compiler wants to track a page view in a web analytics tool:
up.compiler('[track-page-view]', function(element, data, meta) { // mark: meta
// Don't track duplicate page views if we just reloaded for cache revalidation.
if (!meta.revalidating) {
// Send an event to our web analytics tool.
trackPageView(meta.layer.location)
}
})
Servers may inspect request headers to optimize responses,
e.g. by omitting a navigation bar that is not targeted.
Request headers that influenced a response should be listed in a Vary response header,
so Unpoly can partition its cache by header value.
Matching cache entries must then have the same values for all listed headers. See Caching optimized responses for details, and How cache entries are matched for tables of hits and misses.
GET /foo redirects to GET /bar, the response to /bar will be cached for both GET /foo and GET /bar.POST /action, and the response redirects to GET /path,
the browser will make a fresh request to GET /path even if GET /path was cached before.POST /users redirects to GET /users. You can address this by including an X-Up-Method header in your responses.Tip Your coding agent can search these docs locally with the Unpoly agent skill