Skip to content

add caching headers#127

Open
erosika wants to merge 1 commit intov4from
eri/caching-headers
Open

add caching headers#127
erosika wants to merge 1 commit intov4from
eri/caching-headers

Conversation

@erosika
Copy link
Copy Markdown

@erosika erosika commented Feb 11, 2026

add caching headers — same strategy as honcho-landing and evals, adapted for quartz (no content-hashed filenames so js/css use 5min browser cache instead of immutable).

CF edge wasn't caching anything (cf-cache-status: DYNAMIC on html and contentIndex.json). s-maxage fixes this so only the first visitor to a given edge pays origin latency.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

Walkthrough

The PR adds explicit caching directives to the static/_headers file for JavaScript, CSS, static assets, and common image formats, establishing time-bound browser and CDN cache policies. A CSP header formatting adjustment is also included with no semantic changes.

Changes

Cohort / File(s) Summary
Static Headers Configuration
static/_headers
Added cache-control directives for JS/CSS (5 min browser, 1 hour CDN cache), static assets (1 year immutable), and images (1 day browser, 7 day CDN cache). CSP directive formatting adjusted without semantic changes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Neuromancer blog post #106: Both PRs modify the same static/_headers file to adjust security and caching policies, with PR#106 adding CSP frame-src directives.

Suggested reviewers

  • VVoruganti
  • vintrocode

Poem

🐰 With whiskers twitched and nose held high,
I cache the assets way up in the sky—
Five minutes for scripts, a year for the rest,
These headers will help your site perform best! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR description lacks conventional commit prefix and is missing key template sections like detailed change description and LLM disclosure statement. Add a conventional commit prefix (e.g., 'perf:' or 'feat:') to the title and include the required LLM disclosure statement if applicable.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'add caching headers' accurately summarizes the main change: introducing caching configuration rules to the headers file.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch eri/caching-headers

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.

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: 2

🤖 Fix all issues with AI agents
In `@static/_headers`:
- Around line 22-28: The image caching rules currently only include "/*.png",
"/*.webp", and "/*.svg" so common formats like .jpg/.jpeg (and optionally .gif
and .ico) fall back to the short global cache; add equivalent header blocks for
"/*.jpg" and "/*.jpeg" (and optionally "/*.gif" and "/*.ico") with the same
Cache-Control value "public, max-age=86400, stale-while-revalidate=604800" so
favicons and JPEG images get the intended long-lived caching behavior.
- Around line 1-3: The global catch-all header rule `/*` is being merged with
more specific rules (e.g., `/*.js`) causing duplicated Cache-Control values;
update the `static/_headers` rules so specific asset paths override the global
rule by using Cloudflare Pages' override directive: prefix the specific path
rules (like `/*.js`, `/*.css`, etc.) with `! Cache-Control` and then provide the
desired Cache-Control for that path, or remove/limit the `/*` global rule so it
doesn't match asset paths; adjust all affected rules referenced (the `/*` global
rule and per-extension rules such as `/*.js`) to prevent header concatenation by
ensuring only one Cache-Control directive applies per request.
🧹 Nitpick comments (1)
static/_headers (1)

10-10: block-all-mixed-content CSP directive is deprecated.

This directive is redundant since upgrade-insecure-requests is already present (which supersedes it). Browsers are dropping support for block-all-mixed-content. It's harmless today but worth removing to keep the CSP clean.

Comment thread static/_headers
Comment on lines +1 to +3
# HTML pages - short browser cache, longer CDN cache, stale fallback
/*
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n static/_headers

Repository: plastic-labs/blog

Length of output: 1901


🌐 Web query:

Netlify _headers file path specificity precedence rules duplicate headers

💡 Result:

Path matching / specificity (multiple for blocks or multiple path sections in _headers)

  • Netlify effectively applies the most specific matching path rule for a request; headers from a broader rule like /* are not inherited when a more specific rule (for example /foo/*) matches. [1]
  • If you’re trying to “set sitewide, then tweak/override on one route”, you generally must repeat the header on each route where you want it, because the broader rule won’t carry through to more-specific matches. [1]

Duplicate / repeated headers

1) Duplicates inside the same path block in _headers

  • If you list the same header field name multiple times under one path in _headers, Netlify will concatenate them into a single header value (per RFC 7230 behavior). This is the supported way to express multi-value headers in _headers. [2]

2) Duplicates in netlify.toml

  • In netlify.toml, multi-value headers should be expressed as a multiline string (not repeated keys). [2]

3) Same header key set by Netlify defaults vs your custom config

  • Header rules are “cumulative” relative to Netlify’s defaults in the sense that your configured values can override default headers when you set the same header name yourself. [3]

Special case: caching header precedence (if you set multiple cache-control variants)

  • If more than one of Netlify’s supported caching headers is present (for example Netlify-CDN-Cache-Control, CDN-Cache-Control, Cache-Control), Netlify will respect the most specific one for its own CDN behavior (while still forwarding others downstream). [4]

Sources:
[1] Gatsby plugin note describing Netlify _headers behavior (“does not inherit… more specific routes replace”). (gatsbyjs.com)
[2] Netlify Docs: “Multi-value headers” behavior for _headers and netlify.toml. (docs.netlify.com)
[3] Netlify Support: header rules cumulative vs defaults; your values override defaults. (answers.netlify.com)
[4] Netlify Docs: caching “Header precedence” (Netlify-CDN-Cache-Control / CDN-Cache-Control / Cache-Control). (docs.netlify.com)

Citations:


🌐 Web query:

Cloudflare Pages _headers file header precedence merging behavior

💡 Result:

On Cloudflare Pages, _headers rules are merged, not “last rule wins”:

  • If a request URL matches multiple _headers blocks, the response inherits all headers from all matching rules. [1]
  • If the same header name is set by more than one matching rule, Cloudflare joins the values with a comma (it does not override). [1]
  • To remove a header that would otherwise be applied (including one applied by a broader match), use the detach syntax: ! Header-Name. [1]
  • _headers applies to static asset responses; it does not apply to Pages Functions responses (set headers in the Function instead). [1]
  • If a request matches both _redirects and _headers, redirects are applied first (redirect takes priority over headers). [1]

Source: Cloudflare Pages docs: “Headers”. [1]

References

  1. Cloudflare Developers — Pages: Headers documentation. (developers.cloudflare.com)

Citations:


🏁 Script executed:

fd -e toml -e json -e yml | grep -E '(netlify|wrangler|vercel|cloudflare)' | head -20

Repository: plastic-labs/blog

Length of output: 43


🏁 Script executed:

ls -la | grep -E '(netlify|wrangler|vercel)'

Repository: plastic-labs/blog

Length of output: 43


🏁 Script executed:

cat package.json 2>/dev/null | grep -A5 -B5 'scripts\|deploy\|host'

Repository: plastic-labs/blog

Length of output: 425


🏁 Script executed:

find . -maxdepth 2 -type f \( -name "netlify.toml" -o -name "wrangler.toml" -o -name "vercel.json" -o -name ".netlify" -o -name "*.toml" \) 2>/dev/null | head -20

Repository: plastic-labs/blog

Length of output: 43


🏁 Script executed:

cat README.md 2>/dev/null | head -50

Repository: plastic-labs/blog

Length of output: 817


🏁 Script executed:

git remote -v 2>/dev/null

Repository: plastic-labs/blog

Length of output: 183


🏁 Script executed:

find . -type f -path "./.github/workflows/*" | head -10

Repository: plastic-labs/blog

Length of output: 210


🏁 Script executed:

cat .github/workflows/* 2>/dev/null | grep -i "netlify\|cloudflare\|pages\|vercel" | head -20

Repository: plastic-labs/blog

Length of output: 314


🏁 Script executed:

ls -la | head -20

Repository: plastic-labs/blog

Length of output: 1248


Use Cloudflare Pages ! directive to override Cache-Control on specific paths, or avoid the /* global rule.

On Cloudflare Pages, multiple matching path rules are merged. A request for /script.js matches both the global /* rule and the /*.js rule, causing both Cache-Control headers to be concatenated with a comma. This results in a malformed header like Cache-Control: public, max-age=60, ..., public, max-age=300, ..., which breaks caching.

To fix: either prefix specific rules with ! Cache-Control to override the global header (e.g., /*.js becomes ! Cache-Control followed by the desired header), or restructure to avoid the global rule matching cached assets. Verify Cloudflare's header precedence behavior to confirm the approach.

Also applies to: 12-28

🤖 Prompt for AI Agents
In `@static/_headers` around lines 1 - 3, The global catch-all header rule `/*` is
being merged with more specific rules (e.g., `/*.js`) causing duplicated
Cache-Control values; update the `static/_headers` rules so specific asset paths
override the global rule by using Cloudflare Pages' override directive: prefix
the specific path rules (like `/*.js`, `/*.css`, etc.) with `! Cache-Control`
and then provide the desired Cache-Control for that path, or remove/limit the
`/*` global rule so it doesn't match asset paths; adjust all affected rules
referenced (the `/*` global rule and per-extension rules such as `/*.js`) to
prevent header concatenation by ensuring only one Cache-Control directive
applies per request.

Comment thread static/_headers
Comment on lines +22 to +28
# Images
/*.png
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.webp
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.svg
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing common image formats (.jpg, .jpeg, .gif, .ico).

The image caching rules only cover .png, .webp, and .svg. If the site serves .jpg/.jpeg images (very common in blog content) or .ico (favicon), those will fall back to the 60s global cache. Consider adding rules for at least .jpg and .jpeg.

Suggested addition
 /*.svg
   Cache-Control: public, max-age=86400, stale-while-revalidate=604800
+/*.jpg
+  Cache-Control: public, max-age=86400, stale-while-revalidate=604800
+/*.jpeg
+  Cache-Control: public, max-age=86400, stale-while-revalidate=604800
+/*.gif
+  Cache-Control: public, max-age=86400, stale-while-revalidate=604800
+/*.ico
+  Cache-Control: public, max-age=86400, stale-while-revalidate=604800
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Images
/*.png
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.webp
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.svg
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
# Images
/*.png
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.webp
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.svg
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.jpg
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.jpeg
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.gif
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
/*.ico
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
🤖 Prompt for AI Agents
In `@static/_headers` around lines 22 - 28, The image caching rules currently only
include "/*.png", "/*.webp", and "/*.svg" so common formats like .jpg/.jpeg (and
optionally .gif and .ico) fall back to the short global cache; add equivalent
header blocks for "/*.jpg" and "/*.jpeg" (and optionally "/*.gif" and "/*.ico")
with the same Cache-Control value "public, max-age=86400,
stale-while-revalidate=604800" so favicons and JPEG images get the intended
long-lived caching behavior.

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