Skip to main content
Sonamu provides frequently used Cache-Control patterns as CachePresets. Using presets instead of writing configurations manually allows you to easily apply consistent and validated caching strategies.

CachePresets Overview

import { CachePresets } from "sonamu";

// Using in API
@api({
  httpMethod: 'GET',
  cacheControl: CachePresets.shortLived,
})

// Using in SSR
registerSSR({
  path: '/products',
  cacheControl: CachePresets.ssr,
});

Complete Preset List

noStore

No cache storage

noCache

Revalidate every time

shortLived

1 minute cache

ssr

SSR optimized (10 seconds + SWR)

mediumLived

5 minute cache

longLived

1 hour cache

immutable

Permanent cache (static files)

private

Personalized data

Preset Details

noStore

Completely prohibits cache storage.
CachePresets.noStore
// Generated header: Cache-Control: no-store
{
  noStore: true
}

noCache

Stores cache but revalidates every time.
CachePresets.noCache
// Generated header: Cache-Control: no-cache
{
  noCache: true
}
Benefits of no-cache:
1st request: GET /api/data → 200 OK + ETag: "abc123"
2nd request: GET /api/data (If-None-Match: "abc123")
→ 304 Not Modified (no body, fast)

shortLived

1 minute cache - Suitable for frequently changing data.
CachePresets.shortLived
// Generated header: Cache-Control: public, max-age=60
{
  visibility: 'public',
  maxAge: 60  // 60 seconds = 1 minute
}

ssr

SSR page optimization - 10 second cache + Stale-While-Revalidate 30 seconds
CachePresets.ssr
// Generated header: Cache-Control: public, max-age=10, stale-while-revalidate=30
{
  visibility: 'public',
  maxAge: 10,  // Fresh for 10 seconds
  staleWhileRevalidate: 30  // Use stale for the next 30 seconds
}

mediumLived

5 minute cache - Suitable for rarely changing data.
CachePresets.mediumLived
// Generated header: Cache-Control: public, max-age=300
{
  visibility: 'public',
  maxAge: 300  // 300 seconds = 5 minutes
}

longLived

1 hour cache - Suitable for static content.
CachePresets.longLived
// Generated header: Cache-Control: public, max-age=3600
{
  visibility: 'public',
  maxAge: 3600  // 3600 seconds = 1 hour
}

immutable

Permanent cache - For static files with hash
CachePresets.immutable
// Generated header: Cache-Control: public, max-age=31536000, immutable
{
  visibility: 'public',
  maxAge: 31536000,  // 1 year (in seconds)
  immutable: true  // Never changes
}
Benefits of immutable:
  • Uses only cache without revalidation (fastest)
  • When file changes → new hash → new filename → automatic cache update

private

Personalized data - Different response per user
CachePresets.private
// Generated header: Cache-Control: private, no-cache
{
  visibility: 'private',
  noCache: true
}
private vs public:
  • private: Cached only in browser (not on CDN)
  • public: Cached everywhere (browser + CDN)

Preset Comparison Table

PresetHeaderUse CaseTTL
noStoreno-storeSensitive data, MutationsNone
noCacheno-cacheRequires revalidationNone (revalidate)
shortLivedpublic, max-age=60Frequently changes1 minute
ssrpublic, max-age=10, stale-while-revalidate=30SSR pages10 seconds + SWR 30 seconds
mediumLivedpublic, max-age=300Rarely changes5 minutes
longLivedpublic, max-age=3600Static content1 hour
immutablepublic, max-age=31536000, immutableHashed files1 year (permanent)
privateprivate, no-cachePersonalized dataNone (private)

Custom Configuration

If presets don’t fit, you can write your own configuration.
// CDN optimization (Browser: 1 minute, CDN: 5 minutes)
@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 60,      // Browser
    sMaxAge: 300,    // CDN
  }
})
async getData() {
  return this.findMany({});
}

// Adding Stale-If-Error
@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 300,
    staleIfError: 86400,  // Use stale for 1 day on error
  }
})
async getCriticalData() {
  return this.getData();
}

Global Handler

You can dynamically set Cache-Control for all requests.
// sonamu.config.ts
export const config: SonamuConfig = {
  server: {
    apiConfig: {
      cacheControlHandler: (req) => {
        // API requests
        if (req.type === 'api') {
          if (req.method !== 'GET') {
            return CachePresets.noStore;  // Mutations use no-store
          }
          return CachePresets.shortLived;  // GET uses 1 minute cache
        }

        // Static files
        if (req.type === 'assets') {
          if (req.path.includes('-')) {  // Contains hash
            return CachePresets.immutable;
          }
          return CachePresets.longLived;
        }

        // SSR
        if (req.type === 'ssr') {
          return CachePresets.ssr;
        }

        return undefined;  // Default
      }
    }
  }
};

Practical Usage

Strategy by API

class ProductModelClass extends BaseModel {
  // List: Frequently changes → Short cache
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.shortLived,
  })
  async findAll() {
    return this.findMany({});
  }

  // Detail: Less frequent changes → Medium cache
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.mediumLived,
  })
  async findById(id: number) {
    return this.findOne(['id', id]);
  }

  // Categories: Rarely changes → Long cache
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.longLived,
  })
  async getCategories() {
    return this.categories;
  }

  // Create/Update: Mutation → no-store
  @api({
    httpMethod: 'POST',
    cacheControl: CachePresets.noStore,
  })
  async create(data: ProductSave) {
    return this.saveOne(data);
  }
}

SSR Pages

// Homepage: SSR optimized
registerSSR({
  path: '/',
  cacheControl: CachePresets.ssr,
  Component: HomePage,
});

// Product list: Short cache
registerSSR({
  path: '/products',
  cacheControl: CachePresets.shortLived,
  Component: ProductList,
});

// Terms: Long cache
registerSSR({
  path: '/terms',
  cacheControl: CachePresets.longLived,
  Component: Terms,
});

Precautions

Precautions when using presets:
  1. Presets are starting points: Customize as needed
    // ✅ Adjust for your situation
    cacheControl: {
      ...CachePresets.shortLived,
      maxAge: 120,  // Adjusted 1 minute → 2 minutes
    }
    
  2. Only cache GET: Use noStore for POST, PUT, DELETE
    // ❌ Wrong example
    @api({
      httpMethod: 'POST',
      cacheControl: CachePresets.shortLived,
    })
    
    // ✅ Correct example
    @api({
      httpMethod: 'POST',
      cacheControl: CachePresets.noStore,
    })
    
  3. Private data must be private: Prevent CDN caching
    // ❌ Dangerous: Personal data cached on CDN
    @api({
      cacheControl: CachePresets.shortLived,  // public
    })
    async getMyData() { ... }
    
    // ✅ Safe
    @api({
      cacheControl: CachePresets.private,
    })
    async getMyData() { ... }
    
  4. immutable only for hashed files: Must change filename to update
    // ❌ Wrong usage
    // main.js (no hash) → Cache persists even if content changes
    cacheControl: CachePresets.immutable
    
    // ✅ Correct usage
    // main-abc123.js (has hash) → Filename changes when content changes
    cacheControl: CachePresets.immutable
    

Next Steps