| name | nextjs-cost-performance |
| description | Use this skill when optimizing Next.js 16 routes/Server Components/Server Actions for Vercel latency and cost (invocations + duration) using tenant-safe caching and streaming. |
Next.js Cost & Performance Expert
Use this skill when you want a Next.js App Router expert focused on serverless hosting cost + performance efficiency (e.g., Vercel).
This skill is intentionally server-first and tenant-safe. It complements:
- Your project’s quality/performance notes (if present)
- security-strict skill (caching + tenant leakage risks)
- nextjs-server-actions skill (mutation patterns)
- drizzle-tenant-queries skill (query safety)
- bun-workflow skill (how to validate locally)
What “good” looks like (targets)
- Fewer function invocations (better caching / fewer rerenders).
- Lower function duration (fewer round-trips, parallelization, no N+1, less serialization).
- Better perceived speed via streaming (Suspense) and minimal work in layouts.
- Zero tenant leaks: all data access and caching remains workspace-scoped.
Serverless cost model (practical mental model)
- Every Server Component render, Server Action call, and Route Handler can create an invocation.
- Duration is dominated by:
- DB query latency and count,
- sequential waits,
- serialization / large payloads,
- heavy CPU work inside functions.
- Optimization order:
- avoid work (cache or static where correct)
- do less work (smaller queries)
- do work concurrently (Promise.all)
- stream UI (Suspense)
Required safety rules (do not compromise)
- Tenant isolation always
- Never accept
workspaceId from the client.
- Always derive workspace server-side (
lib/workspace.ts).
- Every query/mutation must filter by
workspace_id.
- No shared caching of private data
- Any cross-request caching must include tenant identity in keys/tags.
- If you are not sure a response is safe to cache, default to
no-store.
(Reference: security-strict skill.)
High-impact optimization checklist
A) Find & remove avoidable invocations
- Prefer Server Components by default; keep
'use client' leaf-ish.
- Don’t over-use
revalidatePath() on broad paths.
- Prefer
revalidateTag() with narrow tags.
- Avoid “refresh loops” caused by:
- action triggers → broad revalidate → rerender multiple route segments.
B) Make DB reads cheaper (Drizzle)
- Parallelize independent reads using
Promise.all.
- Avoid N+1:
- do not
for (...) await db.query... to load related records.
- use relations (
with) or a single query with inArray.
- Select only necessary columns (reduce payload + serialization).
- Add indexes in migrations for hot filters:
workspace_id, FKs, timestamps.
C) Cache correctly (3 layers)
- Request-level de-dupe: React
cache()
- Wrap repeated read helpers in
cache() to prevent duplicate queries within a request.
- Use inputs that include
workspaceId.
- Cross-request caching:
unstable_cache (or stable equivalent)
- Use for semi-stable lists/aggregates.
- Always include tenant identity in cache keys.
- Always use tags for invalidation.
- Invalidation after mutations
- In Server Actions, invalidate only the tags that changed.
- Prefer tags like:
clients, projects, time-entries, invoices
- Only use
revalidatePath() when you cannot express invalidation with tags.
Tag taxonomy (recommended)
Prefer a predictable, workspace-scoped tag scheme:
workspace:${workspaceId}:clients
workspace:${workspaceId}:projects
workspace:${workspaceId}:time-entries
workspace:${workspaceId}:invoices
workspace:${workspaceId}:expenses
Rules:
- Never emit non-scoped tags for tenant-private data.
- Keep tag strings stable over time (avoid “random” tags).
When implementing, consider centralizing tag helpers in a dedicated cache-tags module.
D) Route & segment caching (App Router)
Be explicit:
- Dynamic per-user reads:
fetch(..., { cache: 'no-store' })
- Time-based caching:
fetch(..., { next: { revalidate: N } })
- Tag-based caching:
fetch(..., { next: { tags: [...] } }) + revalidateTag(...)
Keep layouts lean:
- Avoid heavy DB reads in root layouts.
- Stream slow UI sections with
<Suspense>.
Recommended working method (how to apply this skill)
- Identify the route or action that is slow/expensive.
- Count server "things":
- How many Server Actions are triggered for one user intent?
- How many DB queries happen in the render?
- Are reads sequential?
- Is there N+1?
- Pick the smallest change that improves one of:
- fewer queries,
- parallelization,
- smaller payload,
- correct caching.
- Add/adjust revalidation (tags first).
- Validate locally (lint/typecheck/tests/build).
Instrumentation (measure before you refactor)
Before large caching changes, add lightweight timing so you can target the biggest wins:
- DB query count per request
- Slowest queries per route (dev-only or sampled)
- Duplicate query detection (same helper called repeatedly)
Keep logs free of PII/secrets; never log full row payloads.
Anti-patterns (treat as blockers)
- Sequential independent DB reads in Server Components.
- N+1 querying in list/table rendering.
- Cross-request caching without tenant-aware keys/tags.
- Broad
revalidatePath() causing extra invocations.
- Marking large route groups
force-dynamic by habit.
- Client components importing server-only modules.
Validation steps (Bun)
Run the standard checks:
bun run lint
bun run typecheck
bun run test
bun run build
Optional repo audits:
bun run i18n:check
bun run scripts/test-client-isolation.ts
(Reference: bun-workflow skill.)
Project pointers (adapt to your repo)
- Performance policy: your project’s performance/quality notes (if present)
- Tenant/workspace derivation: your server-side tenant guard module
- Query helpers: your query helper layer (if present)
- Server Actions: your actions/mutations layer (if present)
- DB schema/migrations: your schema + migrations (if applicable)