Skip to content

fix(nginx): never cache index.html so clients pick up new bundles - #255

Open
rajivsinclair wants to merge 1 commit into
mainfrom
fix/index-html-no-cache
Open

fix(nginx): never cache index.html so clients pick up new bundles#255
rajivsinclair wants to merge 1 commit into
mainfrom
fix/index-html-no-cache

Conversation

@rajivsinclair

@rajivsinclair rajivsinclair commented May 30, 2026

Copy link
Copy Markdown
Contributor

What

Add an exact-match location = /index.html block in nginx.conf that sets Cache-Control: no-store, must-revalidate, so the SPA entrypoint is never cached by browsers. Hashed assets (*.js, *.css, etc.) keep their existing 1y immutable cache.

Why

index.html was served with no Cache-Control header — only Last-Modified/ETag. Browsers then apply heuristic caching (RFC 7234 §4.2.2) and can serve a stale index.html without revalidating. Because index.html references content-hashed bundles (index-<hash>.js), a stale entrypoint points at an old bundle that the new build no longer serves.

This bit us during the Supabase API key rotation (compromised service_role key): the pre-rotation bundle had the legacy anon JWT baked in. After we disabled the legacy keys, clients still loading a cached index.html → old bundle kept sending the dead key and hit:

Legacy API keys are disabled

The origin and the freshly-built bundle (index-sN9AmqBz.js) were already correct — the only stale layer was browser-cached index.html.

Fix

location = /index.html {
    add_header Cache-Control "no-store, must-revalidate";
}

Standard SPA caching strategy: immutable hashed assets + never-cached HTML entrypoint. Every future deploy then takes effect immediately for all clients.

Notes / things to check

  • nginx location precedence: exact match (= /index.html) wins over the prefix location / and the asset regex, so the header applies on direct hits.
  • SPA fallback: try_files ... /index.html and error_page 404 /index.html perform internal redirects that re-match location = /index.html, so fallback-served HTML is also no-store. Worth a sanity check on a preview deploy.
  • No app code changes; purely a serving-header fix.

Verification after deploy

curl -sI https://verdad.app/ | grep -i cache-control
# expect: cache-control: no-store, must-revalidate

Context: follow-up to today's Supabase service_role key rotation incident.

Summary by CodeRabbit

  • Chores
    • Enhanced deployment reliability to ensure users always receive the latest application version, preventing potential issues with outdated content references.

Review Change Stack

index.html had no Cache-Control header, so browsers applied heuristic
caching and could serve a stale entrypoint pointing at an old, deleted
content-hashed bundle. This surfaced during the Supabase key rotation:
clients on a cached index.html kept loading the pre-rotation bundle with
the now-disabled legacy anon key and got 'Legacy API keys are disabled'.

Add an exact-match location for /index.html with no-store so the HTML
entrypoint is always revalidated, while hashed assets keep their 1y
immutable cache. Standard SPA cache strategy.
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7989096b-2494-4691-bed2-dda675439471

📥 Commits

Reviewing files that changed from the base of the PR and between ffeb6cb and eeb0858.

📒 Files selected for processing (1)
  • nginx.conf

Walkthrough

This PR modifies nginx.conf to add an explicit cache control rule for the SPA entrypoint (index.html). A new location block is inserted before the static asset caching rule, setting Cache-Control: no-store, must-revalidate to ensure browsers always fetch the current index.html after deployments and prevent stale entrypoints from referencing removed content-hashed bundles.

Changes

SPA Entrypoint Cache Control

Layer / File(s) Summary
Add SPA entrypoint cache control
nginx.conf
Introduces a dedicated location = /index.html block with Cache-Control: no-store, must-revalidate headers and comments explaining the SPA cache invalidation behavior, ensuring the entrypoint is always refreshed while preserving immutable caching for hashed static assets.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Poem

A little rabbit hops with glee,
"No stale HTML!" declares with cheer,
Cache-Control brings clarity,
Fresh bundles on each deploy appear,
Content-hash and entrypoint—both clear! 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: adding no-cache directives to index.html to ensure clients receive new SPA bundles after deployment.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/index-html-no-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown

✅ Preview Deployment Ready!

URL: https://pr-255-verdad-frontend.fly.dev
Commit: eeb0858


Preview auto-updates on push. Destroyed when PR closes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the Nginx configuration to prevent caching of the SPA entrypoint (index.html) by adding a specific location block with a Cache-Control header. The reviewer suggested appending the 'always' parameter to the add_header directive and adding 'no-cache' to ensure the header is consistently sent across all HTTP status codes (such as 404s) and to maximize compatibility.

Comment thread nginx.conf
# caching and may skip revalidation. no-store forces a fresh fetch every
# load so clients always pick up the current bundle immediately on deploy.
location = /index.html {
add_header Cache-Control "no-store, must-revalidate";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

By default, Nginx's add_header directive only applies to successful responses (like 200 or 301). When a request triggers a 404 (such as a missing static asset falling back to /index.html via error_page 404), Nginx serves index.html with a 404 status code but omits the Cache-Control header. This can cause browsers to cache the 404 response or cache the HTML under a static asset's URL, leading to JavaScript parsing errors. Appending the 'always' parameter ensures the header is sent for all status codes, and adding 'no-cache' ensures maximum compatibility.

        add_header Cache-Control "no-store, no-cache, must-revalidate" always;

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