Views

Caching

Cache controller views, partials, and Turbo frame bodies.

By Guillermo Alvarez - Published - Updated

Use the app cache

Every lazyapp.New application has a cache:

app := lazyapp.New(lazyapp.Config{
    Name:   "sample_app",
    Drawer: Draw,
    Views:  app.Views,
})

When Cache.Backend is omitted, lazyapp uses the in-memory backend from golazy.dev/lazycache/inmemorycache. Direct lazycache.New calls must pass a backend explicitly.

The default in-memory backend is capped at 50 MiB. Set LAZYAPP_CACHE_SIZE to change that cap without changing application source:

LAZYAPP_CACHE_SIZE=500Kb
LAZYAPP_CACHE_SIZE=50Mb
LAZYAPP_CACHE_SIZE=2Gb

Units are case-insensitive, and a value with no unit is interpreted as bytes. The environment variable is used only when the app did not configure lazyapp.Config.Cache.Backend.

You can use the cache from the app value:

err := lazycache.Set(app.Cache, post, "post", post.ID, post.UpdatedAt)

and read typed values:

post, err := lazycache.Get[Post](app.Cache, "post", id, updatedAt)

The cache key is built by joining key parts with -. time.Time values use UTC RFC3339Nano, so timestamps are stable across time zones. Controller and template render caches also include the app build version, and include active render variants when present, so deploys and variant-specific templates do not share stale rendered bodies.

Cache controller renders

Controllers opt a rendered response body into caching with CacheKey:

func (c *PostsController) Show(postID int) error {
    post, err := c.posts.Get(postID)
    if err != nil {
        return err
    }

    if c.CacheKey(post.ID, post.UpdatedAt) {
        return nil
    }
    c.Set("post", post)
    return nil
}

The key includes the app build version, namespace when present, controller, action, request format, and the parts you pass:

build-v0.1.17-admin-posts-show-html-42-2026-06-25T10:30:00Z

Use CacheKeyF when the action should provide the application-specific key parts:

if c.CacheKeyF("post", post.ID, post.UpdatedAt) {
    return nil
}

CacheKey and CacheKeyF return true when the cached body was written, so the action should stop and return nil. On a miss they return false; the normal render path continues and stores the rendered body for the next matching request. Only the rendered body is cached. Headers and status codes still come from the current request.

Cache partials

Use cache around a partial:

{{ cache "featured" "post_card" .post }}

That key is scoped to the build version, active render variants, namespace, controller, action, format, partial name, and local name.

Use cache_key for a full explicit key:

{{ cache (cache_key "post" .post.ID .post.UpdatedAt) "post_card" .post }}

cachef is the same idea without a nested helper call:

{{ cachef "post" .post.ID .post.UpdatedAt "post_card" .post }}

Cache Turbo frame bodies

Turbo frames can cache the rendered frame body while keeping current frame attributes:

{{ turbo_frame "post" .post (cache_key "post" .post.ID .post.UpdatedAt) (turbo_src .post.URL) }}

If the same key is reused later, GoLazy reuses the body and applies the current turbo_frame options again.

Switch caching

Use Off to bypass reads and turn writes into no-ops:

app.Cache.Off()

Use On to resume backend reads and writes. Stats returns the standardized backend counters:

stats := app.Cache.Stats()

In lazy development mode, the Cache tab exposes the same cache state, stats, keys, entry sizes, searchable key table, selected entry content, and On/Off switch through the app's lazydev control plane.

The first backend is in-process and stores Go values. It is useful for a single running process; choose a different backend when multiple app processes must share cached values.