11import { createIPX , ipxHttpStorage , ipxFSStorage , createIPXFetchHandler } from "ipx" ;
2- import { defineHandler } from "nitro/h3" ;
2+ import { defineHandler , getRouterParam } from "nitro/h3" ;
33import { useStorage } from "nitro/storage" ;
44import { hash } from "ohash" ;
55import { env } from "std-env" ;
66
7+ import { IMAGE_TTL , OG_IMAGE_TTL } from "../../utils/constants" ;
8+
79// Parse allowed domains from environment
810export const allowedDomains = env . ALLOWED_DOMAINS
911 ? env . ALLOWED_DOMAINS . split ( "," )
@@ -17,75 +19,54 @@ export const ipx = createIPX({
1719 httpStorage : ipxHttpStorage ( {
1820 domains : allowedDomains ,
1921 allowAllDomains : allowedDomains . length === 0 ,
20- maxAge : 86400 ,
22+ maxAge : IMAGE_TTL ,
2123 } ) ,
22- maxAge : 86400 ,
24+ maxAge : IMAGE_TTL ,
2325} ) ;
2426
25- export function contentTypeToExtension ( contentType : string ) : string {
26- const type = contentType . toLowerCase ( ) ;
27- if ( type . includes ( "png" ) ) return "png" ;
28- if ( type . includes ( "jpeg" ) || type . includes ( "jpg" ) ) return "jpg" ;
29- if ( type . includes ( "webp" ) ) return "webp" ;
30- if ( type . includes ( "gif" ) ) return "gif" ;
31- if ( type . includes ( "avif" ) ) return "avif" ;
32- if ( type . includes ( "heif" ) ) return "heif" ;
33- if ( type . includes ( "tiff" ) ) return "tiff" ;
34- return "png" ;
35- }
27+ const fetchHandler = createIPXFetchHandler ( ipx ) ;
3628
3729export default defineHandler ( async ( event ) => {
30+ const path = getRouterParam ( event , "_" ) || "" ;
31+ const ipxPath = "/" + path ;
3832 const url = new URL ( event . req . url ) ;
39- const ipxPath = url . pathname . replace ( / ^ \/ i m g / , "" ) || "/" ;
4033 url . pathname = ipxPath ;
4134
42- // Generate cache key using ohash
43- const cacheKeyBase = `img:${ hash ( { path : ipxPath , search : url . search } ) } ` ;
35+ const cacheKey = `img:${ hash ( { path : ipxPath , search : url . search } ) } ` ;
4436 const storage = useStorage ( "cache" ) ;
4537
46- // Try common image extensions for cache lookup
47- const extensions = [ "png" , "jpg" , "jpeg" , "webp" , "gif" , "avif" , "heif" , "tiff" , "tif" ] ;
48- let cached : Uint8Array | null = null ;
49- let cacheKey = "" ;
50-
51- for ( const ext of extensions ) {
52- const key = `${ cacheKeyBase } .${ ext } ` ;
53- const data = await storage . getItemRaw < Uint8Array > ( key ) ;
54- if ( data ) {
55- cached = data ;
56- cacheKey = key ;
57- break ;
58- }
59- }
60-
38+ // Cache lookup
39+ const cached = await storage . getItemRaw < Uint8Array > ( cacheKey ) ;
6140 if ( cached ) {
41+ const cachedMeta = await storage . getMeta ( cacheKey ) ;
42+ const contentType =
43+ typeof cachedMeta ?. contentType === "string" ? cachedMeta . contentType : "image/png" ;
44+ event . res . headers . set ( "Content-Type" , contentType ) ;
6245 event . res . headers . set ( "X-Cache" , "HIT" ) ;
63- return Buffer . from ( cached ) ;
46+ event . res . headers . set ( "Cache-Control" , `public, max-age=${ IMAGE_TTL } ` ) ;
47+ return cached ;
6448 }
6549
66- cacheKey = cacheKeyBase ;
67-
68- // Use IPX to process the image
69- const fetchHandler = createIPXFetchHandler ( ipx ) ;
50+ // Process with IPX
7051 const response = await fetchHandler ( new Request ( url , event . req ) ) ;
71- const arrayBuffer = await response . arrayBuffer ( ) ;
72- const buffer = Buffer . from ( arrayBuffer ) ;
52+ const data = await response . bytes ( ) ;
7353
74- // Get extension from response Content-Type
75- const contentType = response . headers . get ( "content-type" ) || "image/png" ;
76- const extension = contentTypeToExtension ( contentType ) ;
77- cacheKey = `${ cacheKey } .${ extension } ` ;
78-
79- // Store raw binary data (7 days)
80- await storage . setItemRaw ( cacheKey , new Uint8Array ( buffer ) ) ;
81- await storage . setMeta ( cacheKey , { ttl : 604800 } ) ; // 7 days
82-
83- // Copy headers from IPX response
54+ // Copy IPX response headers
8455 response . headers . forEach ( ( value , key ) => {
8556 event . res . headers . set ( key , value ) ;
8657 } ) ;
8758
8859 event . res . headers . set ( "X-Cache" , "MISS" ) ;
60+ event . res . headers . set ( "Cache-Control" , `public, max-age=${ IMAGE_TTL } ` ) ;
61+
62+ // Non-blocking cache write: data with TTL, Content-Type in metadata
63+ const contentType = response . headers . get ( "content-type" ) || "image/png" ;
64+ event . waitUntil (
65+ Promise . all ( [
66+ storage . setItemRaw ( cacheKey , data , { ttl : OG_IMAGE_TTL } ) ,
67+ storage . setMeta ( cacheKey , { contentType } , { ttl : OG_IMAGE_TTL } ) ,
68+ ] ) ,
69+ ) ;
8970
90- return buffer ;
71+ return data ;
9172} ) ;
0 commit comments