43 lines
1.6 KiB
JavaScript
43 lines
1.6 KiB
JavaScript
import { lineOf } from '../util.mjs';
|
|
|
|
export const metadata = {
|
|
id: 'max-age-without-s-maxage',
|
|
title: 'Cache-Control: max-age without s-maxage',
|
|
severity: 'medium',
|
|
billingDimension: 'edge-requests',
|
|
trafficIndependent: false,
|
|
description:
|
|
'max-age caches in the browser; s-maxage caches at the CDN. Without s-maxage, every uncached visitor request invokes the function. Adding s-maxage often cuts function invocations by 80%+ on read-heavy routes.',
|
|
fix:
|
|
'Add s-maxage to the Cache-Control header. Example: Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=86400. Pair with explicit cache-bust strategy if content can change.',
|
|
citations: [
|
|
'https://vercel.com/docs/caching/cdn-cache',
|
|
'https://vercel.com/docs/caching/cache-control-headers',
|
|
],
|
|
excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**', '*.config.*'],
|
|
includeGlobs: ['**/*.{ts,tsx,js,jsx,mjs}'],
|
|
};
|
|
|
|
const RE = /Cache-Control[^"'`]*?max-age\s*=\s*\d+/gi;
|
|
|
|
export function scan({ files }) {
|
|
const out = [];
|
|
for (const { path, content } of files) {
|
|
if (/\.test\.|\.spec\./.test(path)) continue;
|
|
let m;
|
|
RE.lastIndex = 0;
|
|
while ((m = RE.exec(content)) !== null) {
|
|
const hit = m[0];
|
|
if (/s-maxage/i.test(hit) || /CDN-Cache-Control/i.test(content.slice(Math.max(0, m.index - 100), m.index + hit.length + 100))) continue;
|
|
out.push({
|
|
pattern: metadata.id,
|
|
file: path,
|
|
line: lineOf(content, m.index),
|
|
evidence: hit.slice(0, 160),
|
|
trafficIndependent: metadata.trafficIndependent,
|
|
});
|
|
}
|
|
}
|
|
return out;
|
|
}
|