Skip to content

feat(docs-cache): purge Netlify CDN tags on invalidation#932

Merged
tannerlinsley merged 1 commit into
mainfrom
taren/xenodochial-pascal-d1f6b3
May 20, 2026
Merged

feat(docs-cache): purge Netlify CDN tags on invalidation#932
tannerlinsley merged 1 commit into
mainfrom
taren/xenodochial-pascal-d1f6b3

Conversation

@tannerlinsley
Copy link
Copy Markdown
Member

@tannerlinsley tannerlinsley commented May 20, 2026

Summary

  • Attach Netlify-Cache-Tag headers to the docs route (/$libraryId/$version/docs/$) and the getTanstackDocsConfig server function so the CDN can be purged surgically.
  • Issue purgeCache({ tags }) from invalidateDocsCacheAdmin and the GitHub push webhook after the existing DB-mark calls.

Why

The DB SWR-on-forced-stale fix only refreshes the cached GitHub content rows. The Netlify CDN still sits in front of the SSR with its own SWR windows (max-age=600, stale-while-revalidate=3600 for latest, 86400/604800 for older versions, plus max-age=300, stale-while-revalidate=300 on the docs config). So even after marking rows stale, an operator clicking "invalidate" — or a real push — still had to wait up to ~10 minutes for the CDN to revalidate the rendered pages. This change closes that window.

Tag scheme

Three tiers so we can purge at any granularity:

Docs routedocs:all, docs:{libraryId}, docs:{libraryId}:branch:{resolvedBranch}
Docs config server fndocs-config:all, docs-config:{repo}, docs-config:{repo}:{branch}

The library-level tag keys off the resolved branch (via getBranch), not the URL version, so a single push invalidates the latest / v5 / main URL variants together.

Purge behavior

  • Webhook ((repo, gitRef)) → docs-config:{repo}:{gitRef} + docs:{libId}:branch:{gitRef} for each matched library.
  • Admin with repodocs-config:{repo} + docs:{libId} for each matched library.
  • Admin without repodocs:all + docs-config:all.

Failure handling

purgeNetlifyTags in src/utils/netlify-purge.server.ts never throws:

  • No-ops when SITE_ID or NETLIFY_PURGE_API_TOKEN is absent (local dev).
  • Logs + sends to Sentry on error. Caller continues — DB rows are already stale and the existing SWR window will eventually catch up.
  • Result is returned in the response payload ({ purge: { purged, ... } }) so the admin UI can surface it.

Operational note

NETLIFY_PURGE_API_TOKEN and SITE_ID are typically auto-injected by Netlify into Functions at runtime — usually nothing to configure. If a manual purge from the admin button returns { purge: { purged: false, reason: 'no-credentials' } }, generate a personal access token with Site:Purge scope and set it as NETLIFY_PURGE_API_TOKEN in the site env.

Test plan

  • pnpm test:tsc passes
  • pnpm test:lint passes (0 errors)
  • pnpm test:smoke passes (all docs/blog routes render)
  • After merge: trigger the admin "Invalidate" button and confirm the response payload includes purge: { purged: true, tags: [...] } and that the docs route serves a fresh response within seconds.
  • After merge: push a doc change to a watched repo and confirm the webhook response shows the purge succeeded and the live page updates immediately.
  • Verify the Netlify env has NETLIFY_PURGE_API_TOKEN; if not, mint one with Site:Purge scope.

Summary by CodeRabbit

  • New Features

    • Implemented tag-based cache invalidation for Netlify documentation
    • GitHub webhooks now automatically purge documentation cache on commits
  • Improvements

    • Documentation cache is now partitioned by library and branch for faster updates
    • Admin cache invalidation enhanced with more granular control

Review Change Stack

The DB-level SWR fix only refreshes the cached GitHub content rows;
the Netlify CDN sits in front of the SSR with its own 10min/1d SWR
windows, so clicking "invalidate" or receiving a push webhook still
left stale rendered pages in front of users for up to ~10 minutes.

Attach Netlify-Cache-Tag headers to the docs route and the
getTanstackDocsConfig server function with three tiers
(docs:all, docs:{libId}, docs:{libId}:branch:{resolvedBranch}) so we
can purge at any granularity. Then issue purgeCache calls from both
invalidateDocsCacheAdmin and the push webhook after the existing DB
mark calls.

Purge keys off the resolved branch (via getBranch), so a single push
invalidates the latest/v5/main URL variants together. Failures are
logged + sent to Sentry but never thrown - the DB rows are already
stale and SWR will eventually catch up. Falls back to a silent no-op
locally when SITE_ID / NETLIFY_PURGE_API_TOKEN are absent.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR implements tag-based cache invalidation for Netlify by introducing a purge utility, tagging cached responses with library and branch identifiers, and integrating purge calls into GitHub webhook and admin invalidation flows.

Changes

Netlify Cache Tag-Based Invalidation

Layer / File(s) Summary
Netlify cache purge utility
src/utils/netlify-purge.server.ts
New purgeNetlifyTags() function deduplicates input tags, validates credentials via environment variables, calls Netlify's purgeCache API, and returns structured results with Sentry error capture on failure.
Cache tag headers for docs responses
src/routes/$libraryId/$version.docs.$.tsx, src/utils/config.ts
Docs route computes cacheTag from library and resolved branch; config endpoint builds tags from repo and branch. Both set netlify-cache-tag response headers to enable tag-based cache partitioning.
GitHub webhook cache purge
src/routes/api/github/webhook.ts
Webhook handler imports libraries and purgeNetlifyTags, builds a list of Netlify tags from affected repos/branches and matching libraries, and calls purgeNetlifyTags() after stale-marking, returning the purge result in the response.
Admin cache invalidation
src/utils/docs-admin.server.ts
Admin invalidation function imports libraries and purgeNetlifyTags, constructs tag lists for targeted repo or global invalidation, calls purgeNetlifyTags(), and includes the purge result in the returned object.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • LadyBluenotes
  • schiller-manuel

🐰 Cache tags bloom and grow,

Netlify purges lay them low,

Docs stay fresh, webhooks flow—

Cache partitioned, swiftly so! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing Netlify CDN tag purging functionality on cache invalidation events.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch taren/xenodochial-pascal-d1f6b3

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/routes/api/github/webhook.ts`:
- Around line 141-149: The purge tag generation currently filters libraries with
library.latestBranch === gitRef which prevents branch pushes from producing CDN
purge tags; in the tags construction (the array using repo, gitRef and
libraries) remove the latestBranch check so that any library where library.repo
=== repo generates a `docs:${library.id}:branch:${gitRef}` tag (i.e., use
libraries.filter(l => l.repo === repo).map(...) instead of also requiring
latestBranch), ensuring pushes to non-latest branches also emit purge tags.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4de71a8d-7af0-40f8-a34c-b8ba5ef5eb59

📥 Commits

Reviewing files that changed from the base of the PR and between 3a136f2 and d19ea6a.

📒 Files selected for processing (5)
  • src/routes/$libraryId/$version.docs.$.tsx
  • src/routes/api/github/webhook.ts
  • src/utils/config.ts
  • src/utils/docs-admin.server.ts
  • src/utils/netlify-purge.server.ts

Comment thread src/routes/api/github/webhook.ts
@tannerlinsley tannerlinsley merged commit 73c6f64 into main May 20, 2026
9 checks passed
@tannerlinsley tannerlinsley deleted the taren/xenodochial-pascal-d1f6b3 branch May 20, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant