Skip to content

Template caching

Any template, from the server or from markup(), can be cached so that it is not produced again for every instance.

Enabling the cache

Caching is opt-in per component:

@component({ tagName: "about-page", cache: true, cacheDuration: 300 })

Without cacheDuration the App's templatesCacheDuration (default 300 seconds) applies. A duration of 0 disables caching for the component.

const app = new MyApp({
    appName: "Example",
    templatesCacheDuration: 300,
    templatesCacheCleanupInterval: 60,
});

What is cached

The cache stores the template: the string returned by markup(), or the HTML returned by the server. It does not store the rendered result. doT expressions ({{= }}, {{? }}, {{~ }}) are still evaluated on every render with the component's current state, and bindings still work.

What the cache freezes is everything that went into producing the template:

  • values interpolated with ${};
  • data that an asynchronous markup() fetched;
  • the server's response.

That makes caching most valuable for server components and for markup() methods that do expensive work:

@component({ tagName: "country-select", cache: true, cacheDuration: 3600 })
export default class CountrySelect extends CustomElement<MyApp> {
    override markup = async () => {
        const response = await fetch("/api/countries");
        const countries: Country[] = await response.json();
        return html`
            <select name="country">
                ${countries.map(country =>
                    `<option value="${country.code}">${country.name}</option>`,
                ).join("")}
            </select>
        `;
    };
}

The country list is fetched once an hour, however many forms show the selector.

Do not tie cached work to one component

The cached factory runs once and its result is shared. Passing this.abortSignal to the request above would let the removal of one component cancel work that other instances are waiting for. JoltUI itself cancels only each component's wait for a shared result, never the shared work.

Cache keys

The default key combines:

  • the tag name;
  • the current route parameters;
  • the current query parameters (sorted);
  • the component's arguments;
  • for server components, also the group name.

Two instances with different arguments, or the same component on different URLs, get different cache entries. Application data is not part of the key, and setData() does not invalidate cached templates.

If the template depends on something else, such as the user's locale or permissions, override templateCacheKey(). It must return a non-empty string:

override templateCacheKey(): string {
    const locale = this.app.getData<string>("locale");
    return `catalog:${locale}:${this.args.category}`;
}

For server components, the method receives the outgoing request as a parameter.

Concurrency

If several components request the same key at once, they share one in-flight operation. A component removed while waiting stops waiting, but the shared request continues for the others and still populates the cache.

Invalidation and cleanup

The application decides when cached templates become invalid: login, logout, an account switch, a permission change, or an update to data the templates are built from:

this.app.setData("user", null);
this.app.clearTemplatesCache();
Member Purpose
clearTemplatesCache() Remove every entry. Requests already running finish for their callers but cannot repopulate the cache.
removeCachedTemplate(key) Remove one entry.
cleanTemplatesCache() Remove expired entries now; returns how many were removed.
templatesCacheCleanupInterval Seconds between automatic cleanups (0 = off); can be changed at runtime.
templatesCacheDuration Default lifetime in seconds; can be changed at runtime.
getCachedTemplate, setCachedTemplate, getOrSetCachedTemplate Low-level access for custom caching.

Expired entries are also removed lazily when they are requested.

Clearing the cache does not abort network requests or re-render components already on screen. After an account change, also navigate away from or re-render the affected components; their lifetime signals cancel their existing work.