@@ -5,6 +5,15 @@ import type { CachedFetchOptions, CacheEntry } from './types';
55// Re-export types for convenience
66export type { CachedFetchOptions , CacheEntry } from './types' ;
77
8+ /**
9+ * Verbose logger that only logs when CACHED_MIDDLEWARE_FETCH_LOGGER=1
10+ */
11+ function verboseLog ( ...args : any [ ] ) : void {
12+ if ( process . env . CACHED_MIDDLEWARE_FETCH_LOGGER === '1' ) {
13+ console . log ( '[cached-middleware-fetch]' , ...args ) ;
14+ }
15+ }
16+
817/**
918 * Process body for cache key generation, matching Next.js behavior
1019 * Note: The body is consumed here for cache key generation only.
@@ -379,8 +388,17 @@ export async function cachedFetch(
379388 const cacheOption = init ?. cache || 'auto no cache' ;
380389 const revalidate = init ?. next ?. revalidate ;
381390
391+ verboseLog ( `Starting cachedFetch: ${ method } ${ url } ` , {
392+ cacheOption,
393+ revalidate,
394+ expires : init ?. next ?. expires ,
395+ tags : init ?. next ?. tags ,
396+ fetchCacheKeyPrefix : init ?. next ?. fetchCacheKeyPrefix
397+ } ) ;
398+
382399 // Skip cache for no-store or revalidate: 0
383400 if ( cacheOption === 'no-store' || revalidate === 0 ) {
401+ verboseLog ( `Skipping cache due to ${ cacheOption === 'no-store' ? 'no-store' : 'revalidate: 0' } ` ) ;
384402 const response = await fetch ( input , cleanOptions ) ;
385403
386404 // Clone the response to avoid body consumption issues
@@ -395,6 +413,7 @@ export async function cachedFetch(
395413 responseWithCacheHeaders . headers . set ( 'X-Cache-Status' , 'MISS' ) ;
396414 responseWithCacheHeaders . headers . set ( 'X-Cache-Age' , '0' ) ;
397415
416+ verboseLog ( `Response: ${ response . status } ${ response . statusText } (cache bypassed)` ) ;
398417 return responseWithCacheHeaders ;
399418 }
400419
@@ -405,12 +424,15 @@ export async function cachedFetch(
405424 }
406425 const cacheKey = await generateCacheKey ( input , cleanOptions , init ?. next ?. fetchCacheKeyPrefix , bodyChunks ) ;
407426
427+ verboseLog ( `Generated cache key: ${ cacheKey } ` ) ;
428+
408429 // Get Vercel Runtime Cache instance
409430 const cache = getCache ( ) ;
410431
411432 try {
412433 // Try to get from cache first
413434 if ( cacheOption === 'force-cache' || cacheOption === 'auto no cache' ) {
435+ verboseLog ( `Looking up cache entry for key: ${ cacheKey } ` ) ;
414436 const cachedEntry = await cache . get ( cacheKey ) as CacheEntry | undefined ;
415437
416438 if (
@@ -420,18 +442,27 @@ export async function cachedFetch(
420442 cachedEntry . headers &&
421443 ! isCacheEntryExpired ( cachedEntry )
422444 ) {
445+ const now = Date . now ( ) ;
446+ const cacheAge = Math . floor ( ( now - cachedEntry . timestamp ) / 1000 ) ;
447+ const expiresIn = cachedEntry . expiresAt ? Math . floor ( ( cachedEntry . expiresAt - now ) / 1000 ) : 'never' ;
448+
423449 // Check if we need to revalidate in the background
424450 const isStale = needsRevalidation ( cachedEntry ) ;
425451 if ( isStale ) {
452+ verboseLog ( `Cache STALE (age: ${ cacheAge } s, expires in: ${ expiresIn } s) - triggering background refresh` ) ;
426453 // Return stale data immediately and refresh in background (SWR)
427454 const backgroundRefresh = async ( ) => {
428455 try {
456+ verboseLog ( `Background refresh started for: ${ url } ` ) ;
429457 const freshResponse = await fetch ( input , cleanOptions ) ;
430458
431459 if ( freshResponse . ok && ( method === 'GET' || method === 'POST' || method === 'PUT' ) ) {
432460 const freshCacheEntry = await responseToCache ( freshResponse . clone ( ) , init ) ;
433461 const cacheTTL = computeTTL ( freshCacheEntry . expiresAt ) ;
434462 await cache . set ( cacheKey , freshCacheEntry , { ttl : cacheTTL } ) ;
463+ verboseLog ( `Background refresh completed and cached (TTL: ${ cacheTTL } s)` ) ;
464+ } else {
465+ verboseLog ( `Background refresh completed but not cached (status: ${ freshResponse . status } , method: ${ method } )` ) ;
435466 }
436467 } catch ( error ) {
437468 console . error ( '[cached-middleware-fetch] Background refresh failed:' , error ) ;
@@ -441,24 +472,41 @@ export async function cachedFetch(
441472 // Use waitUntil to extend the lifetime of the request for background refresh
442473 if ( typeof waitUntil === 'function' ) {
443474 waitUntil ( backgroundRefresh ( ) ) ;
475+ verboseLog ( `Background refresh scheduled with waitUntil` ) ;
444476 } else {
445477 // Fallback if waitUntil is not available (non-Vercel environment)
446478 backgroundRefresh ( ) . catch ( ( ) => { } ) ;
479+ verboseLog ( `Background refresh scheduled as fire-and-forget (no waitUntil available)` ) ;
447480 }
481+ } else {
482+ verboseLog ( `Cache HIT (age: ${ cacheAge } s, expires in: ${ expiresIn } s)` ) ;
448483 }
449484
450485 // Return cached response with appropriate cache status
451486 return cacheToResponse ( cachedEntry , isStale ? 'STALE' : 'HIT' ) ;
487+ } else {
488+ if ( ! cachedEntry ) {
489+ verboseLog ( `Cache MISS - no entry found` ) ;
490+ } else if ( isCacheEntryExpired ( cachedEntry ) ) {
491+ verboseLog ( `Cache MISS - entry expired` ) ;
492+ } else {
493+ verboseLog ( `Cache MISS - entry invalid` ) ;
494+ }
452495 }
496+ } else {
497+ verboseLog ( `Skipping cache lookup due to cache option: ${ cacheOption } ` ) ;
453498 }
454499
455500 // Fetch from origin (cache miss or expired)
501+ verboseLog ( `Fetching from origin: ${ method } ${ url } ` ) ;
456502 const response = await fetch ( input , cleanOptions ) ;
457503
458504 // Clone the response first to avoid body consumption issues
459505 const responseForCaching = response . clone ( ) ;
460506 const responseForReturn = response . clone ( ) ;
461507
508+ verboseLog ( `Origin response: ${ response . status } ${ response . statusText } ` ) ;
509+
462510 // Add cache status headers to indicate this was a miss
463511 const responseWithCacheHeaders = new Response ( responseForReturn . body , {
464512 status : response . status ,
@@ -470,25 +518,32 @@ export async function cachedFetch(
470518
471519 // Only cache successful responses (2xx) and GET/POST/PUT requests
472520 if ( response . ok && ( method === 'GET' || method === 'POST' || method === 'PUT' ) ) {
521+ verboseLog ( `Caching response (status: ${ response . status } , method: ${ method } )` ) ;
473522 const cacheEntry = await responseToCache ( responseForCaching , init ) ;
474523
475524 // Store in cache with appropriate TTL
476525 const cacheTTL = computeTTL ( cacheEntry . expiresAt ) ;
526+ verboseLog ( `Storing in cache with TTL: ${ cacheTTL } s, expires at: ${ cacheEntry . expiresAt ? new Date ( cacheEntry . expiresAt ) . toISOString ( ) : 'never' } ` ) ;
477527
478528 cache . set ( cacheKey , cacheEntry , { ttl : cacheTTL } ) . catch ( ( error : unknown ) => {
479529 console . error ( '[cached-middleware-fetch] Failed to cache response:' , error ) ;
480530 } ) ;
531+ } else {
532+ verboseLog ( `Not caching response (status: ${ response . status } , method: ${ method } , ok: ${ response . ok } )` ) ;
481533 }
482534
483535 return responseWithCacheHeaders ;
484536 } catch ( error ) {
485537 // If cache operations fail, fallback to regular fetch
486538 console . error ( '[cached-middleware-fetch] Cache operation failed:' , error ) ;
539+ verboseLog ( `Falling back to regular fetch due to cache error` ) ;
487540 const fallbackResponse = await fetch ( input , cleanOptions ) ;
488541
489542 // Clone the response to avoid body consumption issues
490543 const fallbackResponseClone = fallbackResponse . clone ( ) ;
491544
545+ verboseLog ( `Fallback response: ${ fallbackResponse . status } ${ fallbackResponse . statusText } ` ) ;
546+
492547 // Add cache status headers to indicate this was a miss due to error
493548 const responseWithCacheHeaders = new Response ( fallbackResponseClone . body , {
494549 status : fallbackResponse . status ,
0 commit comments