Skip to Content
TechnicalCache Tag Convention

Cache Tag Convention

Next.js revalidateTag  is used alongside revalidatePath to invalidate cached data after mutations in the web app. This document defines the tag naming convention, the data sources that are tagged, and the server actions responsible for invalidation.


Tag naming convention

All tags follow a two-level hierarchy that keeps invalidation tenant-isolated:

tenant-scoped list → tenant:{tenantId}:{collection} entity-scoped detail → tenant:{tenantId}:{entity}:{id}

Rules

  1. Always prefix with tenant:{tenantId}.
    This ensures that invalidating one tenant’s data never affects another tenant’s cache.

  2. List tags (e.g. tenant:abc:service-leads) cover all records in a collection for a tenant. Invalidate them after any create, update, or delete that changes list views.

  3. Entity tags (e.g. tenant:abc:service-lead:123) cover a single document. Invalidate them after any write that touches that specific record.

  4. A mutation should invalidate both the entity tag and the list tag so that detail and list views stay consistent.


Tag reference

Helpers are defined in apps/web/lib/cache-tags.ts:

HelperTag producedScope
cacheTags.serviceLeadList(tenantId)tenant:{tenantId}:service-leadsAll leads for a tenant
cacheTags.serviceLead(tenantId, id)tenant:{tenantId}:service-lead:{id}One service lead
cacheTags.proposalList(tenantId)tenant:{tenantId}:proposalsAll proposals for a tenant
cacheTags.proposal(tenantId, id)tenant:{tenantId}:proposal:{id}One proposal

Tagged data sources

Data is tagged at fetch time via unstable_cache wrappers in apps/web/lib/queries/:

Query helperFileTags applied
getLeadById(tenantId, id)lib/queries/service-leads.tsserviceLead, serviceLeadList
getProposalById(tenantId, id)lib/queries/proposals.tsproposal, proposalList

Invalidation call sites

Service leads

Server actionFileTags invalidated
updateLeadOverviewcomponents/service-leads/lead-overview/actions.tsserviceLead(tenantId, leadId), serviceLeadList(tenantId)

The action also calls revalidatePath so that any sub-components that fetch independently (e.g. LeadActivity, LeadProgress) receive fresh data when the route re-renders.

Proposals

Server actionFileTags invalidated
createProposalapp/(app)/proposals/create/actions.tsproposalList(tenantId)
updateProposalapp/(app)/proposals/create/actions.tsproposal(tenantId, id), proposalList(tenantId)

How it works end-to-end

User edits lead description → updateLeadOverview (Server Action) → leadService.update(...) ← writes to MongoDB → revalidateTag(serviceLead tag) ← marks cached getLeadById stale → revalidateTag(serviceLeadList) ← marks list cache stale → revalidatePath(...) ← invalidates full RSC route cache → Next.js navigation to /service-leads/[id] → getLeadById() (unstable_cache miss) ← re-fetches from DB → Page renders fresh data ✓

Adding new domains

To extend this pattern to a new entity:

  1. Add tag helpers to apps/web/lib/cache-tags.ts following the naming rules.
  2. Create a lib/queries/{entity}.ts file that wraps the service findById call in unstable_cache with the appropriate tags.
  3. Use the query helper in the relevant Server Component instead of calling the service directly.
  4. Call revalidateTag with the entity and list tags in every Server Action that mutates that entity.
  5. Update the call-site table in this document.
Last updated on