> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Cache Presets

> Using predefined cache configurations

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

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="noStore" icon="ban">
    No cache storage
  </Card>

  <Card title="noCache" icon="rotate">
    Revalidate every time
  </Card>

  <Card title="shortLived" icon="clock">
    1 minute cache
  </Card>

  <Card title="ssr" icon="window">
    SSR optimized (10 seconds + SWR)
  </Card>

  <Card title="mediumLived" icon="hourglass-half">
    5 minute cache
  </Card>

  <Card title="longLived" icon="hourglass">
    1 hour cache
  </Card>

  <Card title="immutable" icon="lock">
    Permanent cache (static files)
  </Card>

  <Card title="private" icon="user-lock">
    Personalized data
  </Card>
</CardGroup>

## Preset Details

### noStore

**Completely prohibits cache storage**.

```typescript theme={null}
CachePresets.noStore
// Generated header: Cache-Control: no-store
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      noStore: true
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Sensitive data (personal information, payment info)
    * Mutation requests (POST, PUT, DELETE)
    * Real-time data (stock prices, auctions)
    * One-time tokens, OTP
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Payment API
    @api({
      httpMethod: 'POST',
      cacheControl: CachePresets.noStore,
    })
    async createPayment(data: PaymentSave) {
      return this.processPayment(data);
    }

    // Private data retrieval
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.noStore,
    })
    async getPrivateData(ctx: Context) {
      return this.getSensitiveData(ctx.user.id);
    }
    ```
  </Tab>
</Tabs>

### noCache

**Stores cache but revalidates every time**.

```typescript theme={null}
CachePresets.noCache
// Generated header: Cache-Control: no-cache
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      noCache: true
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Data where freshness verification is important
    * When you want to reduce server load but data changes frequently
    * Using conditional requests (ETag, Last-Modified)
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Inventory info (frequently changes)
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.noCache,
    })
    async getInventory(productId: number) {
      return this.checkStock(productId);
    }
    ```
  </Tab>
</Tabs>

**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.

```typescript theme={null}
CachePresets.shortLived
// Generated header: Cache-Control: public, max-age=60
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'public',
      maxAge: 60  // 60 seconds = 1 minute
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Frequently changing API responses
    * Data where real-time accuracy is not critical
    * CSR fallback pages
    * Post lists
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Post list
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.shortLived,
    })
    async getPosts(page: number) {
      return this.findMany({ page, num: 20 });
    }

    // CSR fallback
    registerSSR({
      path: '/*',  // catch-all
      cacheControl: CachePresets.shortLived,
    });
    ```
  </Tab>
</Tabs>

### ssr

**SSR page optimization** - 10 second cache + Stale-While-Revalidate 30 seconds

```typescript theme={null}
CachePresets.ssr
// Generated header: Cache-Control: public, max-age=10, stale-while-revalidate=30
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'public',
      maxAge: 10,  // Fresh for 10 seconds
      staleWhileRevalidate: 30  // Use stale for the next 30 seconds
    }
    ```
  </Tab>

  <Tab title="How It Works" icon="diagram-project">
    **0-10 seconds**: Fresh cache

    * User: Instant response from cache

    **10-40 seconds**: Stale state

    * User: Instant return of stale cache
    * CDN: Background server request → Cache update

    **After 40 seconds**: Expired

    * Server request required
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * SSR pages (product details, blog posts)
    * Dynamic content where slight delay is acceptable
    * Maximize CDN efficiency
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Product detail page
    registerSSR({
      path: '/products/:id',
      cacheControl: CachePresets.ssr,
      Component: ProductDetail,
    });

    // Blog post
    registerSSR({
      path: '/blog/:slug',
      cacheControl: CachePresets.ssr,
      Component: BlogPost,
    });
    ```
  </Tab>
</Tabs>

### mediumLived

**5 minute cache** - Suitable for rarely changing data.

```typescript theme={null}
CachePresets.mediumLived
// Generated header: Cache-Control: public, max-age=300
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'public',
      maxAge: 300  // 300 seconds = 5 minutes
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Configuration data (terms, policies)
    * Category lists
    * Statistical data (daily updates)
    * FAQ, announcements
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Category tree
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.mediumLived,
    })
    async getCategoryTree() {
      return this.buildTree();
    }

    // Terms of service
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.mediumLived,
    })
    async getTerms() {
      return this.findOne(['type', 'terms']);
    }
    ```
  </Tab>
</Tabs>

### longLived

**1 hour cache** - Suitable for static content.

```typescript theme={null}
CachePresets.longLived
// Generated header: Cache-Control: public, max-age=3600
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'public',
      maxAge: 3600  // 3600 seconds = 1 hour
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Static content that rarely changes
    * Images, fonts (files without hash)
    * Reference data (country list, currency codes)
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Country list
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.longLived,
    })
    async getCountries() {
      return this.findMany({});
    }

    // Static configuration
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.longLived,
    })
    async getConfig() {
      return this.config;
    }
    ```
  </Tab>
</Tabs>

### immutable

**Permanent cache** - For static files with hash

```typescript theme={null}
CachePresets.immutable
// Generated header: Cache-Control: public, max-age=31536000, immutable
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'public',
      maxAge: 31536000,  // 1 year (in seconds)
      immutable: true  // Never changes
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Filenames with hash
      * `bundle-abc123.js`
      * `styles-def456.css`
      * `image-789xyz.png`
    * When filename changes if content changes
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // Built static files
    server.get('/assets/:filename', (req, reply) => {
      const filename = req.params.filename;

      // Files with hash use immutable
      if (/\-[a-f0-9]{8,}\.(js|css|png)$/.test(filename)) {
        applyCacheHeaders(reply, CachePresets.immutable);
      }

      return reply.sendFile(filename);
    });
    ```
  </Tab>
</Tabs>

**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

```typescript theme={null}
CachePresets.private
// Generated header: Cache-Control: private, no-cache
```

<Tabs>
  <Tab title="Configuration" icon="gear">
    ```typescript theme={null}
    {
      visibility: 'private',
      noCache: true
    }
    ```
  </Tab>

  <Tab title="When to Use" icon="lightbulb">
    * Per-user data after login
    * My page, profile
    * Shopping cart, order history
    * Personalized recommendations
  </Tab>

  <Tab title="Example" icon="code">
    ```typescript theme={null}
    // My profile
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.private,
    })
    async getMyProfile(ctx: Context) {
      return this.findOne(['id', ctx.user.id]);
    }

    // My order list
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.private,
    })
    async getMyOrders(ctx: Context) {
      return this.findMany({
        where: [['user_id', ctx.user.id]]
      });
    }
    ```
  </Tab>
</Tabs>

**private vs public**:

* `private`: Cached only in browser (not on CDN)
* `public`: Cached everywhere (browser + CDN)

## Preset Comparison Table

| Preset        | Header                                          | Use Case                  | TTL                         |
| ------------- | ----------------------------------------------- | ------------------------- | --------------------------- |
| `noStore`     | `no-store`                                      | Sensitive data, Mutations | None                        |
| `noCache`     | `no-cache`                                      | Requires revalidation     | None (revalidate)           |
| `shortLived`  | `public, max-age=60`                            | Frequently changes        | 1 minute                    |
| `ssr`         | `public, max-age=10, stale-while-revalidate=30` | SSR pages                 | 10 seconds + SWR 30 seconds |
| `mediumLived` | `public, max-age=300`                           | Rarely changes            | 5 minutes                   |
| `longLived`   | `public, max-age=3600`                          | Static content            | 1 hour                      |
| `immutable`   | `public, max-age=31536000, immutable`           | Hashed files              | 1 year (permanent)          |
| `private`     | `private, no-cache`                             | Personalized data         | None (private)              |

## Custom Configuration

If presets don't fit, you can write your own configuration.

```typescript theme={null}
// 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.

```typescript theme={null}
// 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

```typescript theme={null}
class ProductModelClass extends BaseModelClass {
  // 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

```typescript theme={null}
// 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

<Warning>
  **Precautions when using presets**:

  1. **Presets are starting points**: Customize as needed
     ```typescript theme={null}
     // ✅ Adjust for your situation
     cacheControl: {
       ...CachePresets.shortLived,
       maxAge: 120,  // Adjusted 1 minute → 2 minutes
     }
     ```

  2. **Only cache GET**: Use `noStore` for POST, PUT, DELETE
     ```typescript theme={null}
     // ❌ 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
     ```typescript theme={null}
     // ❌ 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
     ```typescript theme={null}
     // ❌ 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
     ```
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="What is Cache-Control?" icon="circle-question" href="/en/advanced-features/cache-control/what-is-cache-control">
    HTTP caching fundamentals
  </Card>

  <Card title="Using in APIs" icon="code" href="/en/advanced-features/cache-control/using-in-apis">
    Apply to @api decorators
  </Card>

  <Card title="Using in SSR" icon="window" href="/en/advanced-features/cache-control/using-in-ssr">
    Apply to SSR pages
  </Card>
</CardGroup>
