11/**
22 * Cloudflare Worker - CORS Proxy for Cambridge Beer Festival Data API
3- *
3+ *
44 * This worker proxies requests to data.cambridgebeerfestival.com and adds
55 * the necessary CORS headers to allow the web app to access the data.
6- *
6+ *
77 * It also serves the festivals.json file which contains festival metadata
88 * and enables dynamic loading of festival drinks.
9+ *
10+ * Cache Strategy:
11+ * - Uses Vary: Origin header to ensure CORS responses are cached per origin
12+ * - CORS preflight cache (Access-Control-Max-Age) is reduced to 10 seconds for
13+ * staging/preview environments for quick recovery from deployment issues
914 */
1015
1116// Import festivals data directly - copied from data/festivals.json during build
@@ -81,7 +86,7 @@ export default {
8186 // Clone the response and add CORS headers
8287 const newHeaders = new Headers ( response . headers ) ;
8388 setCorsHeaders ( newHeaders , request ) ;
84-
89+
8590 // Ensure JSON responses explicitly declare UTF-8 encoding
8691 // This prevents mojibake when non-ASCII characters (é, ö, ä, ñ) are present
8792 const contentType = newHeaders . get ( 'Content-Type' ) ;
@@ -200,13 +205,28 @@ function parseDirectoryListingForBeverageTypes(html) {
200205}
201206
202207function handleCorsPreflight ( request ) {
208+ const origin = request . headers . get ( 'Origin' ) || '' ;
209+
210+ // Set shorter CORS preflight cache for staging/preview environments
211+ // to prevent stale CORS responses after deployments
212+ let maxAge = '300' ; // 5 minutes for production
213+
214+ if ( origin . endsWith ( '.staging-cambeerfestival.pages.dev' ) ||
215+ origin . endsWith ( '.cambeerfestival.pages.dev' ) ||
216+ origin === 'https://staging.cambeerfestival.app' ||
217+ origin . endsWith ( '.trycloudflare.com' ) ||
218+ origin . startsWith ( 'http://localhost' ) ||
219+ origin . startsWith ( 'http://127.0.0.1' ) ) {
220+ maxAge = '10' ; // 10 seconds for development/staging
221+ }
222+
203223 return new Response ( null , {
204224 status : 204 ,
205225 headers : {
206226 ...getCorsHeaders ( request ) ,
207227 'Access-Control-Allow-Methods' : 'GET, OPTIONS' ,
208228 'Access-Control-Allow-Headers' : 'Content-Type' ,
209- 'Access-Control-Max-Age' : '300' , // 5 minutes - allows quick recovery from CORS issues
229+ 'Access-Control-Max-Age' : maxAge ,
210230 } ,
211231 } ) ;
212232}
@@ -219,6 +239,7 @@ function getCorsHeaders(request) {
219239 return {
220240 'Access-Control-Allow-Origin' : origin ,
221241 'Access-Control-Allow-Credentials' : 'true' ,
242+ 'Vary' : 'Origin' , // Tell caches to key by Origin header
222243 } ;
223244 }
224245
@@ -231,6 +252,7 @@ function getCorsHeaders(request) {
231252 return {
232253 'Access-Control-Allow-Origin' : origin ,
233254 'Access-Control-Allow-Credentials' : 'true' ,
255+ 'Vary' : 'Origin' , // Tell caches to key by Origin header
234256 } ;
235257 }
236258
@@ -241,6 +263,7 @@ function getCorsHeaders(request) {
241263 return {
242264 'Access-Control-Allow-Origin' : origin ,
243265 'Access-Control-Allow-Credentials' : 'true' ,
266+ 'Vary' : 'Origin' , // Tell caches to key by Origin header
244267 } ;
245268 }
246269
@@ -252,6 +275,7 @@ function getCorsHeaders(request) {
252275 return {
253276 'Access-Control-Allow-Origin' : origin ,
254277 'Access-Control-Allow-Credentials' : 'true' ,
278+ 'Vary' : 'Origin' , // Tell caches to key by Origin header
255279 } ;
256280 }
257281
0 commit comments