Nts add cache
Skill juncoding/nextjs-trpc-prisma-starter/skills/nts-add-cache
Claude Code plugin: scaffold and maintain lightweight internal management systems on Next.js + tRPC + Prisma in SPA mode
npx -y skills add juncoding/nextjs-trpc-prisma-starter --skill nts-add-cacheAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Retrofit Redis caching into an existing project scaffolded with nextjs-trpc-prisma-starter. Use this when the user wants to add Redis, add a cache layer, speed up frequent reads, share cache across processes, or any 'add caching to this project' request. Adds ioredis, a docker-compose redis service, a typed cache helper at src/server/lib/cache.ts, REDIS_URL env, and updates CLAUDE.md to reflect the new dependency. Refuses to run on projects that don't have the scaffolded structure.
SKILL.md
5.5 KB, as published. Nobody here has run it
Add Redis cache to an existing project
For projects scaffolded with nextjs-trpc-prisma-starter that didn't enable cache at scaffold time and now want it.
Use when
- User says "add Redis" / "add caching" / "speed up this query" with the implication that in-process cache isn't enough.
- A specific service method needs cross-process cache (multiple app containers / horizontal scaling).
- User invokes
/nts-add-cache.
Do NOT use when:
- The project isn't scaffolded by this plugin — the file layout assumptions won't hold. Refuse gracefully and explain.
- The user wants Next.js's built-in
unstable_cache/cacheTag— this plugin uses SPA mode, so those aren't relevant.
What this skill does
Modifies the existing project in place. After running:
ioredisadded topackage.jsondependencies.redisservice added todocker-compose.yml.REDIS_URLadded to.env.exampleandsrc/env.ts.- New file
src/server/lib/cache.tswith a typedcache.get<T>()/cache.set()/cache.invalidate()helper. CLAUDE.mdupdated to mention the new cache layer.- Optional: a sample service method wrapped in the cache helper, so the user has a worked example.
Confirmation flow
Before writing anything, confirm:
- Project root path (default: cwd).
- Verify
package.json,CLAUDE.md, anddocker-compose.ymlexist — refuse if they don't. - Show the user the list of files that will change.
- Confirm.
File changes
package.json
Add to dependencies:
"ioredis": "^5"
docker-compose.yml
Add a redis service alongside postgres:
redis:
image: redis:7-alpine
container_name: {{PROJECT_NAME}}-redis
ports: ["6379:6379"]
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5
And add redis-data: to the volumes: block at the bottom.
.env.example
Append (with section header):
# Cache
REDIS_URL=redis://localhost:6379
src/env.ts
Add REDIS_URL: z.string().url() to the server schema and REDIS_URL: process.env.REDIS_URL to runtime.
src/server/lib/cache.ts (new)
import "server-only";
import Redis from "ioredis";
import { env } from "@/env";
const redis = new Redis(env.REDIS_URL, { lazyConnect: true, maxRetriesPerRequest: 3 });
// Lazy connect — the connection only opens on first command.
interface CacheOpts {
/** TTL in seconds. Required — pick a value, no infinite caching. */
ttlSeconds: number;
}
export const cache = {
async get<T>(key: string): Promise<T | null> {
const raw = await redis.get(key);
if (!raw) return null;
try { return JSON.parse(raw) as T; } catch { return null; }
},
async set<T>(key: string, value: T, opts: CacheOpts) {
await redis.set(key, JSON.stringify(value), "EX", opts.ttlSeconds);
},
async invalidate(key: string) {
await redis.del(key);
},
/** Pattern-based invalidation. Use sparingly — KEYS is O(N) on the keyspace. */
async invalidatePattern(pattern: string) {
const keys = await redis.keys(pattern);
if (keys.length > 0) await redis.del(...keys);
},
/** Memoize an async fn. Cache hit returns immediately; miss runs `fn` and stores. */
async memo<T>(key: string, opts: CacheOpts, fn: () => Promise<T>): Promise<T> {
const cached = await cache.get<T>(key);
if (cached !== null) return cached;
const fresh = await fn();
await cache.set(key, fresh, opts);
return fresh;
},
};
CLAUDE.md
Locate the stack section and add Cache: line. Locate the "Useful commands" section and add docker compose up redis if not present.
Worked example to show the user
After the install lands, show this pattern as the "how to actually use it" example:
// src/server/modules/exchange-rate/exchange-rate.service.ts
import { cache } from "@/server/lib/cache";
export const exchangeRateService = {
async getRateMap(userId: string) {
await requirePermission(userId, "exchange-rate:read");
return cache.memo("exchange-rate:map", { ttlSeconds: 300 }, async () => {
const rows = await db.exchangeRate.findMany();
return Object.fromEntries(rows.map(r => [r.currency, r.rate]));
});
},
async updateRate(userId: string, currency: string, rate: number) {
await requirePermission(userId, "exchange-rate:write");
await db.$transaction(async (tx) => {
await tx.exchangeRate.upsert({ where: { currency }, create: { currency, rate }, update: { rate } });
await auditLog(tx, { userId, action: "exchange-rate.updated", entityId: currency });
});
await cache.invalidate("exchange-rate:map"); // invalidate after the tx commits
},
};
Note: invalidate AFTER the transaction commits, not inside it — if the tx rolls back, you'd otherwise have invalidated a stale entry pointlessly.
Verification
After writing all files, run:
pnpm install
docker compose up -d redis
pnpm tsc --noEmit
Report any failures. Don't try to run the app — the user owns that.
Gives 0 of the 12 instructions most databases sql skills give
Counted across 589 of the 662 authors here whose files we hold, read 2026-08-06
- use parameterized queriesin 36 of 589, across 32 files
- use timestamptz for timestampsin 30 of 589, across 12 files
- create indexes concurrentlyin 29 of 589, across 23 files
- index foreign keysin 28 of 589, across 17 files
- use numeric type for moneyin 25 of 589, across 8 files
- select only required columnsin 24 of 589, across 19 files
- use cursor pagination instead of OFFSETin 23 of 589, across 15 files
- add indexes manually on foreign key columnsin 22 of 589, across 11 files
- read individual rule files for detailed explanationsin 18 of 589, across 4 files
- configure connection poolingin 18 of 589, across 16 files
- put equality columns before range columns in indexesin 17 of 589, across 9 files
- normalize to third normal formin 17 of 589, across 8 files
Said here and by no other author read
- Verify required files exist before proceeding
- Show the user the list of files that will change
- Obtain confirmation before writing anything
- Add ioredis dependency
- Add redis service to docker-compose
- Add REDIS_URL to environment files
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.