Skip to contents

cache() memoises a portion of a template as an HTML tag subtree. The contents are computed once per unique cache key, and reused in subsequent calls. This avoids repeat evaluation of expensive or stable HTML trees.

clear_cache() removes all memoised templates from freshwater's cache store.

invalidate_cache() removes a single cached entry identified by name, and optionally via vary and fragment values. Note that the invalidate_cache arguments must match those in the original cache call, as they are used to construct the cache key.

invalidate_cache_here() is the in-template version of invalidate_cache. It uses the current template execution context to allow users to forcibly regenerate caches inside the template function.

Usage

cache(name, vary = NULL, ...)

clear_cache()

invalidate_cache(tpl, name, vary = NULL, fragment = NULL)

invalidate_cache_here(name, vary = NULL, fragment = NULL)

Arguments

name

unique name for the cached partial template

vary

values that should change when the cached output should change. This is used to construct the cache key.

...

tag content to render and cache

tpl

a template function created by template().

fragment

optional fragment name for targetting cached fragments

Details

Caches may be freely nested, as each cache is scoped to the template context it is executed in.

Caching occurs a small overhead for first-time usage, but is faster in proceeding calls.

Caching is powered by memoise::memoise. Cache storage limits, eviction, and persistence are controlled via the underlying memoise/cache backend.

If telemetry from otel is enabled, cache hit and miss events are recorded on the current active span (typically the route-level span made by routr). Hit and miss counts are also measured as metrics when enabled.

Note: invalidation affects future renders only. Calling this within the cache() block that is being targeted will not result in an invalidation of the cache.

Examples

# Caching
nav <- template(user, {
  div(
    cache(
      "nav",
      vary = user$id,
      ul(
        li("Home"),
        li("Profile"),
        if (user$is_admin) li("Admin")
      )
    )
  )
})
nav(list(id = 1, is_admin = TRUE))
#> <div>
#>   <ul>
#>     <li>Home</li>
#>     <li>Profile</li>
#>     <li>Admin</li>
#>   </ul>
#> </div>

# Nested Caches
dashboard <- template(page = list(), stats = list(), recent = list(), {
    cache(
        name = "page",
        vary = page$updated_at,
        div(
            h1("Dashboard"),
            cache(
                name = "stats",
                 vary = stats$updated_at,
                div(p(stats$count))
            ),
            cache(
                name = "recent",
                vary = recent$updated_at,
                div(recent)
            )
        )
    )
})
dashboard()
#> <div>
#>   <h1>Dashboard</h1>
#>   <div>
#>     <p></p>
#>   </div>
#>   <div></div>
#> </div>

# TTL-caching (time-based invalidation)
page <- template({
  cache(
    name = "clock",
    vary = memoise::timeout(60L),
    div(sprintf("Generated at %s", Sys.time()))
  )
})
page()
#> <div>Generated at 2026-04-03 22:07:49.912463</div>

# Invalidate the current cache
# during rendering

page <- template(user, {
  div(
    cache(
      name = "content",
      vary = user$id,
      {
        if (user$refresh) {
          invalidate_cache_here(
            name = "content",
            vary = user$id
          )
        }
        p("Hello ", user$id)
      }
    )
  )
})

page(list(id = 1, refresh = FALSE))
#> <div>
#>   <p>
#>     Hello 
#>     1
#>   </p>
#> </div>

page(list(id = 1, refresh = TRUE))
#> <div>
#>   <p>
#>     Hello 
#>     1
#>   </p>
#> </div>