Skip to content

Decide how cached pages are invalidated when the data changes #117

Description

@davidtaing

#53 puts the directory and /p/[handle] behind Next's cache. That is correct for a read-heavy public directory, but it means a revoked badge or a withdrawn profile can keep being served from a cache after the database says otherwise — the failure AGENTS.md describes as silent, fails-open and indistinguishable from a working directory. This issue decides how the application invalidates a cached page when the data behind it changes.

#53 names the tags — practitioner:<id> and practitioners — and does nothing else with them, because it has no write path to purge from. Everything below is what #14 needs before its writes are correct.

What the Next 16 docs settle, and what they leave open

Established by reading node_modules/next/dist/docs, because most published advice on this predates 16:

  • updateTag(tag) expires immediately. The next request waits for fresh data. Server Actions only — not Route Handlers, not Client Components.
  • revalidateTag(tag, "max") marks stale and serves stale-while-revalidate — the old content goes out once more while fresh data is fetched behind it. Callable from Server Functions and Route Handlers.
  • Bare revalidateTag(tag) with no second argument is deprecated.
  • Tags must be attached to the cached data first, either with fetch(url, { next: { tags: [...] } }) or with cacheTag() inside a 'use cache' function.
  • unstable_cache has been replaced by use cache in 16.

The difference between the two purges is load-bearing here, not a tuning choice. Stale-while-revalidate is right for a bio edit and wrong for revocation: it serves the pulled badge or the withdrawn profile exactly one more time. So revocation wants updateTag, which constrains it to a Server Action — consistent with #72, which already requires every admin action to go through a Server Action calling the RPC rather than a client writing to PostgREST.

The decision

How do tags get attached to a Supabase query? We query through supabase-js, so there is no fetch call of ours to hang next.tags on.

  1. Enable cacheComponents: true and use 'use cache' with cacheTag(). The sanctioned Next 16 route. Costs a config change with application-wide caching semantics, which is why it is a decision and not a detail.
  2. Wrap the client's fetchcreateClient accepts global.fetch — and inject next.tags. No config change, but varying tags per query through a shared client is awkward and easy to get subtly wrong.

Pick one and record why. Whichever wins, revalidate stays as the backstop clock rather than the freshness mechanism.

Also decide: does a practitioner's own edit purge?

#53 specifies purging on status and verified changes, both Bluehex-owned. It says nothing about a practitioner rewriting their bio, which falls through to the clock — so they edit, see no change, and reasonably conclude it is broken. This is the read-your-own-writes case updateTag exists for. It is cheap to include and easy to forget.

The gap this does not close by itself

Anything changing data outside the applicationpsql, the Supabase dashboard, a future background job — purges nothing, and the cache goes stale with no signal. Closing it needs a Supabase database webhook calling a Route Handler that calls revalidateTag (a Route Handler cannot use updateTag), authenticated with a shared secret so it is not an open purge endpoint anyone can use to stampede the cache.

Worth deciding whether to close it now or to write down that the application is the only sanctioned writer.

Done when

  • The tagging mechanism is chosen, implemented, and the reasoning recorded.
  • Revocation expires immediately rather than serving one more stale response, and there is a test that would fail if someone swapped it for stale-while-revalidate.
  • It is settled whether a practitioner's own edit purges their page.
  • The out-of-band gap is either closed with an authenticated webhook or documented as accepted.

Related: #53 (defines the tag names), #14 (the writes that need this), #72 (already requires admin actions to be Server Actions).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: appApplication code outside UI and schemaenhancementNew feature or requesthitlNeeds a human (decision/design/review)size: MA weekend for someone new to this codebase

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions