Skip to content

feat(cache): set Cache-Control for sirv static assets and SSR#2969

Merged
eldadfux merged 1 commit intomainfrom
fix/docs-cls-toc-multicode
May 5, 2026
Merged

feat(cache): set Cache-Control for sirv static assets and SSR#2969
eldadfux merged 1 commit intomainfrom
fix/docs-cls-toc-multicode

Conversation

@eldadfux
Copy link
Copy Markdown
Member

@eldadfux eldadfux commented May 5, 2026

Share rules in server/cache-static.js. server/main.js wraps the adapter handler because build/client is served before SvelteKit handle. hooks apply the same policy to Kit/SSR responses when no Cache-Control is set yet.

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Share rules in server/cache-static.js. server/main.js wraps the adapter handler
because build/client is served before SvelteKit handle. hooks apply the same
policy to Kit/SSR responses when no Cache-Control is set yet.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR extracts the cacheControlForPath logic and cache constants from src/hooks.server.ts into a shared server/cache-static.js module, then wires it into server/main.js to also cover sirv-served static assets (which bypass SvelteKit hooks entirely). The approach is correct — attachStaticCacheControl monkey-patches writeHead/write/end on the Node ServerResponse, with an applied flag and a getHeader guard that reliably prevent double-application when both the wrapper and the hook path are active for the same request.

  • server/cache-static.js: New shared module with cache constants, cacheControlForPath, pathnameFromRequest (with decodeURIComponent + URL parsing), and attachStaticCacheControl (response patching).
  • server/main.js: Wraps the adapter handler so sirv responses for build/client assets receive Cache-Control headers before SvelteKit's hooks are ever involved.
  • src/hooks.server.ts: Drops the now-duplicate local implementation and imports from the shared module; staticAssetCache behaviour is unchanged.

Confidence Score: 4/5

Safe to merge — the change is a clean refactoring of existing cache logic into a shared module, with the only new runtime behaviour being that sirv-served static assets now also receive Cache-Control headers.

The core patching logic in attachStaticCacheControl is carefully guarded with an applied flag, a getHeader check, and a status-code whitelist, and the hooks path is unchanged in behaviour. The two inline findings are stylistic: a trivial variable alias and a dead-code fallback branch in the writeHead patch. Nothing here affects correctness or data flow.

Only server/cache-static.js warrants a second look — specifically the attachStaticCacheControl patching logic — but the guards appear sound for the sirv use-case.

Important Files Changed

Filename Overview
server/cache-static.js New shared module exporting cache constants, cacheControlForPath, pathnameFromRequest, and attachStaticCacheControl (response monkey-patching). Logic is sound; a couple of minor nits (dead-code fallback, trivial alias).
server/main.js Wraps the adapter handler with wrapAdapterHandlerWithStaticCache to inject Cache-Control for sirv-served static assets before SvelteKit handles anything. Change is minimal and correct.
src/hooks.server.ts Removes the duplicate local cacheControlForPath and constants; imports them from the new shared module. staticAssetCache logic is unchanged and correct.
eslint.config.js Adds an ESLint override to disable @typescript-eslint/ban-ts-comment for server/cache-static.js, allowing the // @ts-nocheck directive on that file.

Reviews (1): Last reviewed commit: "feat(cache): set Cache-Control for sirv ..." | Re-trigger Greptile

Comment thread server/cache-static.js
Comment on lines +58 to +69
if (!directive) return;
const policy = directive;

let applied = false;

/** @param {number} statusCode */
function tryApply(statusCode) {
if (applied || res.headersSent || res.getHeader('Cache-Control')) return;
if (statusCode !== 200 && statusCode !== 206 && statusCode !== 304) return;
applied = true;
res.setHeader('Cache-Control', policy);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The policy variable is an unnecessary alias — directive is already validated non-null by the early return on line 58, so it can be used directly throughout the rest of the function. This is dead indirection.

Suggested change
if (!directive) return;
const policy = directive;
let applied = false;
/** @param {number} statusCode */
function tryApply(statusCode) {
if (applied || res.headersSent || res.getHeader('Cache-Control')) return;
if (statusCode !== 200 && statusCode !== 206 && statusCode !== 304) return;
applied = true;
res.setHeader('Cache-Control', policy);
}
if (!directive) return;
let applied = false;
/** @param {number} statusCode */
function tryApply(statusCode) {
if (applied || res.headersSent || res.getHeader('Cache-Control')) return;
if (statusCode !== 200 && statusCode !== 206 && statusCode !== 304) return;
applied = true;
res.setHeader('Cache-Control', directive);
}

Comment thread server/cache-static.js
Comment on lines +72 to +79
res.writeHead = (...args) => {
if (!res.headersSent) {
const first = args[0];
const code = typeof first === 'number' ? first : (res.statusCode ?? 200);
tryApply(code);
}
return origWriteHead(...args);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The typeof first === 'number' guard and its fallback are dead code. Node.js ServerResponse.writeHead requires the status code as the first argument — it is always a number. The fallback branch (res.statusCode ?? 200) can never be reached.

Suggested change
res.writeHead = (...args) => {
if (!res.headersSent) {
const first = args[0];
const code = typeof first === 'number' ? first : (res.statusCode ?? 200);
tryApply(code);
}
return origWriteHead(...args);
};
res.writeHead = (...args) => {
if (!res.headersSent) {
tryApply(args[0]);
}
return origWriteHead(...args);
};

@eldadfux eldadfux merged commit 87d7a1d into main May 5, 2026
6 of 7 checks passed
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