npm package: @karmsakha/next-vps-cache
Description: Redis-first cacheHandlers utility for self-hosted Next.js 16+ with hardened serialization and fallback memory cache.
Author: KarmSakha
Latest version: 1.0.0

# @karmsakha/next-vps-cache

[![CI](https://img.shields.io/github/actions/workflow/status/karmsakha/next-vps-cache/ci.yml?branch=main)](https://github.com/karmsakha/next-vps-cache/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/@karmsakha/next-vps-cache)](https://www.npmjs.com/package/@karmsakha/next-vps-cache)
[![license](https://img.shields.io/npm/l/@karmsakha/next-vps-cache)](./LICENSE)
[![coverage](https://img.shields.io/badge/coverage-vitest%20local-blue)](#development)

Redis-first cache utilities for self-hosted Next.js 16+ (`cacheHandlers`) with:

- hardened stream-safe serialization/deserialization
- runtime shape validation with repair hints
- tiered Redis + memory fallback behavior
- structured metrics/log hooks for observability

## Why this exists

Self-hosted Next.js deployments can fail hard when custom cache payloads drift from expected stream/binary shapes. This package prevents response-pipeline crashes by validating and normalizing cache entries before they are returned to Next internals.

## Install

```bash
npm install @karmsakha/next-vps-cache
```

## Compatibility Matrix

| Layer | Supported |
| --- | --- |
| Next.js | `>=16.1.x` |
| Node.js | `>=18.17` |
| Redis | `>=6` (tested with Redis 7) |
| Runtime | Node.js self-hosted VPS (non-edge) |

## Quick Start (Next.js 16+ `cacheHandlers`)

`next.config.ts`

```ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  cacheHandlers: {
    default: require.resolve("./cache-handlers/default"),
    remote: require.resolve("./cache-handlers/remote"),
  },
};

export default nextConfig;
```

`cache-handlers/default.ts`

```ts
import {
  createTieredVpsCacheHandler,
  createConsoleLogger,
} from "@karmsakha/next-vps-cache";

const logger = createConsoleLogger({ level: "info", prefix: "next-cache" });

const handler = createTieredVpsCacheHandler({
  redis: {
    url: process.env.REDIS_URL,
    keyPrefix: "myapp:next:",
    logger,
  },
  fallback: {
    maxItems: 5000,
    maxTotalBytes: 256 * 1024 * 1024,
    logger,
  },
  logger,
});

export default handler;
```

`cache-handlers/remote.ts`

```ts
import {
  createRedisVpsCacheHandler,
  createConsoleLogger,
} from "@karmsakha/next-vps-cache";

export default createRedisVpsCacheHandler({
  url: process.env.REDIS_URL,
  keyPrefix: "myapp:next:",
  logger: createConsoleLogger({ level: "info", prefix: "next-cache-remote" }),
});
```

## API (stable for `v1.x`)

### `createRedisVpsCacheHandler(options)`
Returns a Next-compatible handler backed by Redis (`ioredis`).

### `createMemoryFallbackCacheHandler(options?)`
Returns a bounded in-memory handler for local fallback.

### `createTieredVpsCacheHandler(options)`
Returns a tiered handler: Redis primary + memory fallback.

### `serializeCacheEntry(entry)` / `deserializeCacheEntry(raw)`
Stream-safe serializer/deserializer for cache entry persistence.

### `validateCacheEntryShape(entry)`
Validates and optionally repairs known malformed shapes.

### Types

- `RedisVpsCacheOptions`
- `FallbackOptions`
- `SerializationOptions`
- `VpsCacheMetricsEvent`
- `VpsCacheLogger`

## Migration Guide (deprecated custom handlers -> `cacheHandlers`)

1. Move old custom-cache logic out of deprecated adapters.
2. Create explicit `cache-handlers/default.ts` and `cache-handlers/remote.ts`.
3. Use `createTieredVpsCacheHandler` for default route cache and resilience.
4. Keep `createRedisVpsCacheHandler` for remote-only use where required.
5. Enable observability via `logger.emit(event)` and track:
   - `hit`, `miss`, `fallback-hit`, `deserialize-fail`, `redis-timeout`

## Troubleshooting Matrix

| Symptom | Likely cause | What to check | Action |
| --- | --- | --- | --- |
| Opaque stream/pipe runtime failure | malformed cached `value` shape | `deserialize-fail` / `deserialize-repair` events | verify serializer usage, clear bad keys |
| High fallback ratio | Redis connectivity/timeouts | `redis-timeout` events, Redis latency | increase timeout budget, inspect network |
| High miss rate despite cache writes | key prefix mismatch or expiry drift | keyPrefix config, `expire/revalidate` fields | align keyPrefix and ttl logic |
| Slow TTFB during incident | unbounded in-memory fallback | fallback `maxItems` / `maxTotalBytes` | set explicit bounds and monitor evictions |

## Observability

Events emitted through `logger.emit(event)` include:

- `hit`
- `miss`
- `stale`
- `set`
- `fallback-hit`
- `redis-timeout`
- `deserialize-repair`
- `deserialize-fail`

## Benchmark Baseline

Use the included script with `autocannon`:

```bash
npm run benchmark:baseline -- --url http://127.0.0.1:3000 --connections 50 --duration 30
```

It writes a JSON summary to `./reports/` by default.

## Development

```bash
npm run typecheck
npm run test
npm run test:coverage
npm run build
```

## Security and Support

- Security policy: [SECURITY.md](./SECURITY.md)
- Contributing guide: [CONTRIBUTING.md](./CONTRIBUTING.md)
- Changelog: [CHANGELOG.md](./CHANGELOG.md)