11import fs from "node:fs" ;
2+ import path from "node:path" ;
3+ import crypto from "node:crypto" ;
24
35import type { APIRoute , GetStaticPaths } from "astro" ;
46import { defaultFonts , generate } from "ezog" ;
57import sharp from "sharp" ;
68
79import config from "../../config" ;
10+ import { CACHE_DIR } from "../../constants" ;
811import { getAllPosts , getPostOnly } from "../../notion" ;
912
1013const fonts = defaultFonts ( 700 ) ;
1114const ogBaseBuffer = await fs . promises
1215 . readFile ( config . og ?. baseImagePath || "public/og-base.png" )
1316 . catch ( ( ) => null ) ;
1417
18+ // Generate hash of base image for cache invalidation
19+ const ogBaseHash = ogBaseBuffer
20+ ? crypto . createHash ( "sha256" ) . update ( ogBaseBuffer ) . digest ( "hex" )
21+ : "no-base" ;
22+
23+ // Generate hash of ezog configuration for cache invalidation
24+ const imageWidth = 1200 ;
25+ const imageHeight = 630 ;
26+ const imagePadding = 80 ;
27+ const fontSize = 60 ;
28+ const lineHeight = 80 ;
29+
30+ const configHash = crypto
31+ . createHash ( "sha256" )
32+ . update (
33+ JSON . stringify ( {
34+ imageWidth,
35+ imageHeight,
36+ imagePadding,
37+ fontSize,
38+ lineHeight,
39+ backgroundColor : config . og ?. backgroundColor || "#fff" ,
40+ titleStyle : config . og ?. titleStyle || { } ,
41+ } ) ,
42+ )
43+ . digest ( "hex" ) ;
44+
45+ const cacheDir = path . join ( CACHE_DIR , "og-images" ) ;
46+
47+ // Ensure cache directory exists
48+ await fs . promises . mkdir ( cacheDir , { recursive : true } ) . catch ( ( ) => { } ) ;
49+
1550export type Props = {
1651 slug : string ;
1752} ;
@@ -21,16 +56,26 @@ export const getStaticPaths: GetStaticPaths = async () => {
2156 return posts . map ( ( p ) => ( { params : { slug : p . slug } } ) ) ?? [ ] ;
2257} ;
2358
24- const imageWidth = 1200 ;
25- const imageHeight = 630 ;
26- const imagePadding = 80 ;
27- const fontSize = 60 ;
28- const lineHeight = 80 ;
29-
3059export const GET : APIRoute < Props > = async ( { params } ) => {
3160 const post = await getPostOnly ( params . slug || "" ) ;
3261 if ( ! post ) throw new Error ( "Post not found" ) ;
3362
63+ // Generate cache key hash based on post title, base image, and configuration
64+ // All inputs are hashed together to create a unique cache filename
65+ const cacheKey = crypto
66+ . createHash ( "sha256" )
67+ . update ( `${ post . title } -${ ogBaseHash } -${ configHash } ` )
68+ . digest ( "hex" ) ;
69+ const cachePath = path . join ( cacheDir , `${ cacheKey } .webp` ) ;
70+
71+ // Check if cached file exists
72+ try {
73+ const cachedBuffer = await fs . promises . readFile ( cachePath ) ;
74+ return new Response ( new Uint8Array ( cachedBuffer ) ) ;
75+ } catch {
76+ // Cache miss, continue to generate
77+ }
78+
3479 const image = await generate (
3580 [
3681 ...( ogBaseBuffer
@@ -68,5 +113,9 @@ export const GET: APIRoute<Props> = async ({ params }) => {
68113 ) ;
69114
70115 const webp = await sharp ( image ) . webp ( ) . toBuffer ( ) ;
116+
117+ // Save to cache
118+ await fs . promises . writeFile ( cachePath , webp ) . catch ( ( ) => { } ) ;
119+
71120 return new Response ( new Uint8Array ( webp ) ) ;
72121} ;
0 commit comments