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.

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.