my website at ewancroft.uk
at main 3.2 kB view raw
1import type { Handle } from '@sveltejs/kit'; 2import { PUBLIC_CORS_ALLOWED_ORIGINS } from '$env/static/public'; 3import { HTTP_CACHE_HEADERS } from '$lib/config/cache.config'; 4 5/** 6 * Global request handler with CORS support 7 * 8 * CORS headers are dynamically configured via the PUBLIC_CORS_ALLOWED_ORIGINS environment variable. 9 * Set it to a comma-separated list of allowed origins, or "*" to allow all origins. 10 */ 11export const handle: Handle = async ({ event, resolve }) => { 12 // Handle OPTIONS preflight requests for CORS 13 if (event.request.method === 'OPTIONS' && event.url.pathname.startsWith('/api/')) { 14 const origin = event.request.headers.get('origin'); 15 const allowedOrigins = PUBLIC_CORS_ALLOWED_ORIGINS?.split(',').map((o) => o.trim()) || []; 16 17 const headers: Record<string, string> = { 18 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 19 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 20 'Access-Control-Max-Age': '86400' 21 }; 22 23 if (allowedOrigins.includes('*')) { 24 headers['Access-Control-Allow-Origin'] = '*'; 25 } else if (origin && allowedOrigins.includes(origin)) { 26 headers['Access-Control-Allow-Origin'] = origin; 27 headers['Vary'] = 'Origin'; 28 } 29 30 return new Response(null, { status: 204, headers }); 31 } 32 33 const response = await resolve(event, { 34 filterSerializedResponseHeaders: (name) => { 35 return name === 'content-type' || name === 'cache-control' || name.startsWith('x-'); 36 } 37 }); 38 39 // Add HTTP caching headers for better performance and reduced timeouts 40 // Layout data (root route) is cached aggressively since profile/site info changes infrequently 41 if (!event.url.pathname.startsWith('/api/')) { 42 // Root layout loads profile and site info - cache aggressively 43 if (event.url.pathname === '/' || event.url.pathname === '') { 44 response.headers.set('Cache-Control', HTTP_CACHE_HEADERS.LAYOUT); 45 } 46 // Blog listing pages 47 else if (event.url.pathname.startsWith('/blog') || event.url.pathname.startsWith('/archive')) { 48 response.headers.set('Cache-Control', HTTP_CACHE_HEADERS.BLOG_LISTING); 49 } 50 // Individual blog post pages 51 else if (event.url.pathname.match(/^\/[a-z0-9-]+$/)) { 52 response.headers.set('Cache-Control', HTTP_CACHE_HEADERS.BLOG_POST); 53 } 54 // Other pages get moderate caching 55 else { 56 response.headers.set('Cache-Control', HTTP_CACHE_HEADERS.LAYOUT); 57 } 58 } 59 60 // Add CORS headers for API routes 61 if (event.url.pathname.startsWith('/api/')) { 62 const origin = event.request.headers.get('origin'); 63 const allowedOrigins = PUBLIC_CORS_ALLOWED_ORIGINS?.split(',').map((o) => o.trim()) || []; 64 65 // If * is specified, allow any origin 66 if (allowedOrigins.includes('*')) { 67 response.headers.set('Access-Control-Allow-Origin', '*'); 68 } else if (origin && allowedOrigins.includes(origin)) { 69 // Only set the specific origin if it's in the allowed list 70 response.headers.set('Access-Control-Allow-Origin', origin); 71 response.headers.set('Vary', 'Origin'); 72 } 73 74 response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 75 response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 76 response.headers.set('Access-Control-Max-Age', '86400'); // 24 hours 77 } 78 79 return response; 80};