my website at ewancroft.uk
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};