@@ -39,6 +39,13 @@ export interface AnalyticsOptions {
3939 facebookPixelId ?: string ;
4040}
4141
42+ export interface CachingOptions {
43+ enabled : boolean ;
44+ htmlTtl ?: number ;
45+ staticAssetsTtl ?: number ;
46+ imageTtl ?: number ;
47+ }
48+
4249export interface CustomHtmlOptions {
4350 headerHtml ?: string ;
4451}
@@ -73,6 +80,7 @@ export interface CodeData {
7380 branding : BrandingOptions ;
7481 seo : SeoOptions ;
7582 analytics : AnalyticsOptions ;
83+ caching : CachingOptions ;
7684 customHtml : CustomHtmlOptions ;
7785 custom404 : Custom404Options ;
7886 subdomainRedirects : SubdomainRedirect [ ] ;
@@ -105,6 +113,7 @@ export default function code(data: CodeData): string {
105113 branding,
106114 seo,
107115 analytics,
116+ caching,
108117 customHtml,
109118 custom404,
110119 subdomainRedirects,
@@ -174,6 +183,15 @@ ${slugs
174183 const GOOGLE_TAG_ID = '${ analytics ?. googleTagId || "" } ';
175184 const FACEBOOK_PIXEL_ID = '${ analytics ?. facebookPixelId || "" } ';
176185
186+ /*
187+ * Step 3.5.1: caching configuration (optional)
188+ * Add Cache-Control headers for better performance
189+ */
190+ const CACHING_ENABLED = ${ caching ?. enabled || false } ;
191+ const HTML_TTL = ${ caching ?. htmlTtl || 60 } ;
192+ const STATIC_ASSETS_TTL = ${ caching ?. staticAssetsTtl || 86400 } ;
193+ const IMAGE_TTL = ${ caching ?. imageTtl || 604800 } ;
194+
177195 /*
178196 * Step 3.6: custom HTML header injection (optional)
179197 * Add custom HTML to the top of the page body (e.g., navigation, announcements)
273291 return options;
274292 }
275293
294+ // Apply Cache-Control headers based on content type (Issue #33)
295+ function applyCacheHeaders(response, url, contentType) {
296+ if (!CACHING_ENABLED) return response;
297+
298+ const newResponse = new Response(response.body, response);
299+ const pathname = url.pathname;
300+
301+ // Static assets (JS, CSS, fonts)
302+ if (pathname.match(/\\.(js|css|woff2?|ttf|eot)$/)) {
303+ newResponse.headers.set('Cache-Control', \`public, max-age=\${STATIC_ASSETS_TTL}, immutable\`);
304+ }
305+ // Images
306+ else if (pathname.startsWith('/image') || pathname.match(/\\.(jpg|jpeg|png|gif|webp|avif|svg|ico)$/)) {
307+ newResponse.headers.set('Cache-Control', \`public, max-age=\${IMAGE_TTL}\`);
308+ }
309+ // HTML pages
310+ else if (!contentType || contentType.includes('text/html')) {
311+ newResponse.headers.set('Cache-Control', \`public, max-age=\${HTML_TTL}, stale-while-revalidate=60\`);
312+ }
313+
314+ return newResponse;
315+ }
316+
276317 function generateSitemap() {
277318 let sitemap = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
278319 slugs.forEach(
375416 body = rewriteDomainInBody(body);
376417 response = new Response(body, response);
377418 response.headers.set('Content-Type', 'application/x-javascript');
378- return response;
419+ return applyCacheHeaders( response, url, 'application/javascript') ;
379420 } else if (url.pathname.startsWith('/_assets/') && url.pathname.endsWith('.js')) {
380421 // Handle Notion's new asset paths
381422 response = await fetch(url.toString());
382423 let body = await response.text();
383424 body = rewriteDomainInBody(body);
384425 response = new Response(body, response);
385426 response.headers.set('Content-Type', 'application/javascript');
386- return response;
427+ return applyCacheHeaders( response, url, 'application/javascript') ;
387428 } else if (url.pathname.startsWith('/api/v3/getPublicPageData')) {
388429 // Proxy getPublicPageData and rewrite domain info
389430 response = await fetch(url.toString(), {
450491 return response;
451492 } else if (url.pathname.startsWith('/image') && IMAGE_OPTIMIZATION !== 'none') {
452493 const response = await fetch(url, rewriteImageOptions());
453- return response;
494+ return applyCacheHeaders( response, url, 'image') ;
454495 } else if (slugs.indexOf(url.pathname.slice(1)) > -1) {
455496 const pageId = SLUG_TO_PAGE[url.pathname.slice(1)];
456497 return Response.redirect('https://' + MY_DOMAIN + '/' + pageId, 302);
481522 });
482523 response.headers.delete('Content-Security-Policy');
483524 response.headers.delete('X-Content-Security-Policy');
484- return appendJavascript(response, SLUG_TO_PAGE, '404');
525+ return appendJavascript(response, SLUG_TO_PAGE, '404', url );
485526 }
486527
487528 // Get current slug from page ID for canonical URL
488529 const pageId = url.pathname.slice(-32);
489530 const currentSlug = PAGE_TO_SLUG[pageId] || '';
490531
491- return appendJavascript(response, SLUG_TO_PAGE, currentSlug);
532+ return appendJavascript(response, SLUG_TO_PAGE, currentSlug, url );
492533 }
493534
494535 class MetaRewriter {
798839 }
799840 }
800841
801- async function appendJavascript(res, SLUG_TO_PAGE, slug) {
842+ async function appendJavascript(res, SLUG_TO_PAGE, slug, url ) {
802843 const metaRewriter = new MetaRewriter(slug);
803844 const headRewriter = new HeadRewriter(slug);
804845 const linkRewriter = new LinkRewriter();
805- return new HTMLRewriter()
846+ const contentType = res.headers.get('content-type');
847+ let transformed = new HTMLRewriter()
806848 .on('title', metaRewriter)
807849 .on('meta', metaRewriter)
808850 .on('link', linkRewriter)
809851 .on('head', headRewriter)
810852 .on('body', new BodyRewriter(SLUG_TO_PAGE))
811853 .transform(res);
854+ return applyCacheHeaders(transformed, url, contentType);
812855 }` ;
813856}
0 commit comments