[{"data":1,"prerenderedAt":3868},["ShallowReactive",2],{"blog-post:open-graph-images-using-satori":3},{"id":4,"title":5,"body":6,"date":3858,"description":3859,"draft":3860,"extension":1974,"lastModified":3861,"meta":3862,"navigation":169,"path":3863,"readingTime":3864,"seo":3865,"stem":3866,"__hash__":3867},"posts\u002Fblog\u002Fopen-graph-images-using-satori.md","Dynamic OG images using Satori and Astro Server Endpoints",{"type":7,"value":8,"toc":3849},"minimark",[9,13,35,56,61,70,98,109,119,123,135,205,211,387,400,707,722,1072,1078,1086,1097,1104,1108,1119,1145,1244,1253,1382,1388,1402,1406,1412,1516,1525,1539,1548,1557,1565,1576,1580,1591,1920,1923,1941,1945,1956,1970,2006,2014,2020,2095,2114,2119,2165,2182,2566,2569,3189,3199,3206,3210,3218,3225,3233,3240,3347,3358,3363,3796,3803,3813,3820,3823,3845],[10,11,12],"p",{},"Generating Open Graph images would usually require a separate service or a\nserver with a headless browser that renders the layout HTML template (after\nloading custom fonts, images etc) and generate a PNG image of the page which\nwould then be sent to the client. This solution is inherently very slow and\nresource intensive.",[10,14,15,16,26,27,34],{},"To solve this problem Vercel has launched a package called\n",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Fvercel.com\u002Fdocs\u002Fconcepts\u002Ffunctions\u002Fedge-functions\u002Fog-image-generation",[21],"nofollow",[23,24,25],"code",{},"@vercel\u002Fog","\nthat works seamlessly with Next.js and Vercel Edge Functions. Internally it uses\nanother package by vercel called ",[17,28,31],{"href":29,"rel":30},"https:\u002F\u002Fgithub.com\u002Fvercel\u002Fsatori",[21],[23,32,33],{},"satori","\nthat converts HTML and CSS into SVG and the best part about it is it supports\nJSX, TailwindCSS and custom fonts out of the box.",[10,36,37,38,43,44,51,52,55],{},"We can use this package in our\n",[17,39,42],{"href":40,"rel":41},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fcore-concepts\u002Fendpoints\u002F#server-endpoints-api-routes",[21],"Astro Server Endpoints","\nto generate Dynamic Open Graph Images. But Satori expects JSX as input and since\nwe cannot directly write JSX in our Astro Server Endpoints we'll use a package\ncalled ",[17,45,48],{"href":46,"rel":47},"https:\u002F\u002Fgithub.com\u002Fnatemoo-re\u002Fsatori-html",[21],[23,49,50],{},"satori-html"," by Nate Moore\nthat basically helps us write html in template strings and gives back Satori\ncompatible ",[23,53,54],{},"VNode"," that can be passed directly to Satori and generate our Open\nGraph image as SVG.",[57,58,60],"h2",{"id":59},"initial-setup","Initial Setup",[10,62,63,64,66,67,69],{},"Let's add ",[23,65,33],{}," and ",[23,68,50],{}," to our Astro Project.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-sh shiki shiki-themes github-dark","npm install satori satori-html\n","sh","",[23,78,79],{"__ignoreMap":76},[80,81,84,88,92,95],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"svObZ","npm",[80,89,91],{"class":90},"sU2Wk"," install",[80,93,94],{"class":90}," satori",[80,96,97],{"class":90}," satori-html\n",[10,99,100,101,104,105,108],{},"I'll create an ",[23,102,103],{},"index.og.ts"," file that corresponds to Astro Server API endpoint\n",[23,106,107],{},"\u002Findex.og"," in the Source Root Directory.",[10,110,111],{},[112,113],"img",{"alt":114,"dataZoomable":76,"height":115,"loading":116,"src":117,"width":118},"Initial Project Setup",692,"lazy","\u002Fcontent\u002Finitial_project_setup.png",522,[57,120,122],{"id":121},"generate-open-graph-svg","Generate Open Graph SVG",[10,124,125,126,131,132,134],{},"Let's create a ",[17,127,130],{"href":128,"rel":129},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fguides\u002Fendpoints\u002F#server-endpoints-api-routes",[21],"Astro Server Endpoint"," GET route in the ",[23,133,103],{}," file that\nwould eventually return our dynamic open graph image.",[71,136,140],{"className":137,"code":138,"language":139,"meta":76,"style":76},"language-ts shiki shiki-themes github-dark","import type { APIContext } from \"astro\";\n\nexport async function GET({ params }: APIContext) {}\n","ts",[23,141,142,164,171],{"__ignoreMap":76},[80,143,144,148,151,155,158,161],{"class":82,"line":83},[80,145,147],{"class":146},"snl16","import",[80,149,150],{"class":146}," type",[80,152,154],{"class":153},"s95oV"," { APIContext } ",[80,156,157],{"class":146},"from",[80,159,160],{"class":90}," \"astro\"",[80,162,163],{"class":153},";\n",[80,165,167],{"class":82,"line":166},2,[80,168,170],{"emptyLinePlaceholder":169},true,"\n",[80,172,174,177,180,183,186,189,193,196,199,202],{"class":82,"line":173},3,[80,175,176],{"class":146},"export",[80,178,179],{"class":146}," async",[80,181,182],{"class":146}," function",[80,184,185],{"class":86}," GET",[80,187,188],{"class":153},"({ ",[80,190,192],{"class":191},"s9osk","params",[80,194,195],{"class":153}," }",[80,197,198],{"class":146},":",[80,200,201],{"class":86}," APIContext",[80,203,204],{"class":153},") {}\n",[10,206,207,208,210],{},"Now lets use write our html with inline-styles that would become the Open Graph\nimage and pass it to ",[23,209,50],{},".",[71,212,214],{"className":137,"code":213,"language":139,"meta":76,"style":76},"import type { APIContext } from \"astro\";\nimport { html } from \"satori-html\";\n\nexport async function GET({ params }: APIContext) {\n  const markup = html(`\n  \u003Cdiv\n    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n  >\n    \u003Cdiv\n      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n    >\n      \u003Cspan\n        >Hello from\n        \u003Cspan style=\"margin-left:15ch;color: rgb(255,93,1);\"\n          >Astro\u003C\u002Fspan\n        >\u003C\u002Fspan\n      >\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>`);\n};\n",[23,215,216,230,244,248,272,294,300,306,312,318,324,330,336,342,348,354,360,366,372,381],{"__ignoreMap":76},[80,217,218,220,222,224,226,228],{"class":82,"line":83},[80,219,147],{"class":146},[80,221,150],{"class":146},[80,223,154],{"class":153},[80,225,157],{"class":146},[80,227,160],{"class":90},[80,229,163],{"class":153},[80,231,232,234,237,239,242],{"class":82,"line":166},[80,233,147],{"class":146},[80,235,236],{"class":153}," { html } ",[80,238,157],{"class":146},[80,240,241],{"class":90}," \"satori-html\"",[80,243,163],{"class":153},[80,245,246],{"class":82,"line":173},[80,247,170],{"emptyLinePlaceholder":169},[80,249,251,253,255,257,259,261,263,265,267,269],{"class":82,"line":250},4,[80,252,176],{"class":146},[80,254,179],{"class":146},[80,256,182],{"class":146},[80,258,185],{"class":86},[80,260,188],{"class":153},[80,262,192],{"class":191},[80,264,195],{"class":153},[80,266,198],{"class":146},[80,268,201],{"class":86},[80,270,271],{"class":153},") {\n",[80,273,275,278,282,285,288,291],{"class":82,"line":274},5,[80,276,277],{"class":146},"  const",[80,279,281],{"class":280},"sDLfK"," markup",[80,283,284],{"class":146}," =",[80,286,287],{"class":86}," html",[80,289,290],{"class":153},"(",[80,292,293],{"class":90},"`\n",[80,295,297],{"class":82,"line":296},6,[80,298,299],{"class":90},"  \u003Cdiv\n",[80,301,303],{"class":82,"line":302},7,[80,304,305],{"class":90},"    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n",[80,307,309],{"class":82,"line":308},8,[80,310,311],{"class":90},"  >\n",[80,313,315],{"class":82,"line":314},9,[80,316,317],{"class":90},"    \u003Cdiv\n",[80,319,321],{"class":82,"line":320},10,[80,322,323],{"class":90},"      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n",[80,325,327],{"class":82,"line":326},11,[80,328,329],{"class":90},"    >\n",[80,331,333],{"class":82,"line":332},12,[80,334,335],{"class":90},"      \u003Cspan\n",[80,337,339],{"class":82,"line":338},13,[80,340,341],{"class":90},"        >Hello from\n",[80,343,345],{"class":82,"line":344},14,[80,346,347],{"class":90},"        \u003Cspan style=\"margin-left:15ch;color: rgb(255,93,1);\"\n",[80,349,351],{"class":82,"line":350},15,[80,352,353],{"class":90},"          >Astro\u003C\u002Fspan\n",[80,355,357],{"class":82,"line":356},16,[80,358,359],{"class":90},"        >\u003C\u002Fspan\n",[80,361,363],{"class":82,"line":362},17,[80,364,365],{"class":90},"      >\n",[80,367,369],{"class":82,"line":368},18,[80,370,371],{"class":90},"    \u003C\u002Fdiv>\n",[80,373,375,378],{"class":82,"line":374},19,[80,376,377],{"class":90},"  \u003C\u002Fdiv>`",[80,379,380],{"class":153},");\n",[80,382,384],{"class":82,"line":383},20,[80,385,386],{"class":153},"};\n",[10,388,389,390,393,394,397,398,210],{},"I have a custom font file saved in ",[23,391,392],{},"\u002Fpublic\u002Ffonts\u002F"," directory. We'll use\n",[23,395,396],{},"readFileSync"," method from standard Node.js API to load the custom font file and\ngenerate our SVG using ",[23,399,33],{},[71,401,403],{"className":137,"code":402,"language":139,"meta":76,"style":76},"import type { APIContext } from \"astro\";\nimport { html } from \"satori-html\";\nimport { readFileSync } from \"fs\";\n\nexport async function GET({ params }: APIContext) {\n  const markup = html(`\n  \u003Cdiv\n    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n  >\n    \u003Cdiv\n      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n    >\n      \u003Cspan\n        >Hello from\n        \u003Cspan style=\"margin-left:15ch;color: rgb(255,93,1);\"\n          >Astro\u003C\u002Fspan\n        >\u003C\u002Fspan\n      >\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>`);\n\n  const fontFilePath = `${process.cwd()}\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf`;\n\n  const fontFile = readFileSync(fontFilePath);\n\n  const svg = await satori(markup, {\n    width: 1200,\n    height: 630,\n    fonts: [\n      {\n        name: \"Optimistic Display\",\n        data: fontFile,\n        style: \"normal\",\n      },\n    ],\n  });\n};\n",[23,404,405,419,431,445,449,471,485,489,493,497,501,505,509,513,517,521,525,529,533,537,543,548,577,582,598,603,621,633,644,650,656,667,673,684,690,696,702],{"__ignoreMap":76},[80,406,407,409,411,413,415,417],{"class":82,"line":83},[80,408,147],{"class":146},[80,410,150],{"class":146},[80,412,154],{"class":153},[80,414,157],{"class":146},[80,416,160],{"class":90},[80,418,163],{"class":153},[80,420,421,423,425,427,429],{"class":82,"line":166},[80,422,147],{"class":146},[80,424,236],{"class":153},[80,426,157],{"class":146},[80,428,241],{"class":90},[80,430,163],{"class":153},[80,432,433,435,438,440,443],{"class":82,"line":173},[80,434,147],{"class":146},[80,436,437],{"class":153}," { readFileSync } ",[80,439,157],{"class":146},[80,441,442],{"class":90}," \"fs\"",[80,444,163],{"class":153},[80,446,447],{"class":82,"line":250},[80,448,170],{"emptyLinePlaceholder":169},[80,450,451,453,455,457,459,461,463,465,467,469],{"class":82,"line":274},[80,452,176],{"class":146},[80,454,179],{"class":146},[80,456,182],{"class":146},[80,458,185],{"class":86},[80,460,188],{"class":153},[80,462,192],{"class":191},[80,464,195],{"class":153},[80,466,198],{"class":146},[80,468,201],{"class":86},[80,470,271],{"class":153},[80,472,473,475,477,479,481,483],{"class":82,"line":296},[80,474,277],{"class":146},[80,476,281],{"class":280},[80,478,284],{"class":146},[80,480,287],{"class":86},[80,482,290],{"class":153},[80,484,293],{"class":90},[80,486,487],{"class":82,"line":302},[80,488,299],{"class":90},[80,490,491],{"class":82,"line":308},[80,492,305],{"class":90},[80,494,495],{"class":82,"line":314},[80,496,311],{"class":90},[80,498,499],{"class":82,"line":320},[80,500,317],{"class":90},[80,502,503],{"class":82,"line":326},[80,504,323],{"class":90},[80,506,507],{"class":82,"line":332},[80,508,329],{"class":90},[80,510,511],{"class":82,"line":338},[80,512,335],{"class":90},[80,514,515],{"class":82,"line":344},[80,516,341],{"class":90},[80,518,519],{"class":82,"line":350},[80,520,347],{"class":90},[80,522,523],{"class":82,"line":356},[80,524,353],{"class":90},[80,526,527],{"class":82,"line":362},[80,528,359],{"class":90},[80,530,531],{"class":82,"line":368},[80,532,365],{"class":90},[80,534,535],{"class":82,"line":374},[80,536,371],{"class":90},[80,538,539,541],{"class":82,"line":383},[80,540,377],{"class":90},[80,542,380],{"class":153},[80,544,546],{"class":82,"line":545},21,[80,547,170],{"emptyLinePlaceholder":169},[80,549,551,553,556,558,561,564,566,569,572,575],{"class":82,"line":550},22,[80,552,277],{"class":146},[80,554,555],{"class":280}," fontFilePath",[80,557,284],{"class":146},[80,559,560],{"class":90}," `${",[80,562,563],{"class":153},"process",[80,565,210],{"class":90},[80,567,568],{"class":86},"cwd",[80,570,571],{"class":90},"()",[80,573,574],{"class":90},"}\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf`",[80,576,163],{"class":153},[80,578,580],{"class":82,"line":579},23,[80,581,170],{"emptyLinePlaceholder":169},[80,583,585,587,590,592,595],{"class":82,"line":584},24,[80,586,277],{"class":146},[80,588,589],{"class":280}," fontFile",[80,591,284],{"class":146},[80,593,594],{"class":86}," readFileSync",[80,596,597],{"class":153},"(fontFilePath);\n",[80,599,601],{"class":82,"line":600},25,[80,602,170],{"emptyLinePlaceholder":169},[80,604,606,608,611,613,616,618],{"class":82,"line":605},26,[80,607,277],{"class":146},[80,609,610],{"class":280}," svg",[80,612,284],{"class":146},[80,614,615],{"class":146}," await",[80,617,94],{"class":86},[80,619,620],{"class":153},"(markup, {\n",[80,622,624,627,630],{"class":82,"line":623},27,[80,625,626],{"class":153},"    width: ",[80,628,629],{"class":280},"1200",[80,631,632],{"class":153},",\n",[80,634,636,639,642],{"class":82,"line":635},28,[80,637,638],{"class":153},"    height: ",[80,640,641],{"class":280},"630",[80,643,632],{"class":153},[80,645,647],{"class":82,"line":646},29,[80,648,649],{"class":153},"    fonts: [\n",[80,651,653],{"class":82,"line":652},30,[80,654,655],{"class":153},"      {\n",[80,657,659,662,665],{"class":82,"line":658},31,[80,660,661],{"class":153},"        name: ",[80,663,664],{"class":90},"\"Optimistic Display\"",[80,666,632],{"class":153},[80,668,670],{"class":82,"line":669},32,[80,671,672],{"class":153},"        data: fontFile,\n",[80,674,676,679,682],{"class":82,"line":675},33,[80,677,678],{"class":153},"        style: ",[80,680,681],{"class":90},"\"normal\"",[80,683,632],{"class":153},[80,685,687],{"class":82,"line":686},34,[80,688,689],{"class":153},"      },\n",[80,691,693],{"class":82,"line":692},35,[80,694,695],{"class":153},"    ],\n",[80,697,699],{"class":82,"line":698},36,[80,700,701],{"class":153},"  });\n",[80,703,705],{"class":82,"line":704},37,[80,706,386],{"class":153},[10,708,709,710,713,714,721],{},"Let's test if this works by returning the resulting ",[23,711,712],{},"svg"," from satori as text\n",[17,715,718],{"href":716,"rel":717},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FResponse",[21],[23,719,720],{},"Response"," from our\nGET API route.",[71,723,725],{"className":137,"code":724,"language":139,"meta":76,"style":76},"import type { APIContext } from \"astro\";\nimport { html } from \"satori-html\";\nimport { readFileSync } from \"fs\";\nimport satori from \"satori\";\n\nexport async function GET({ params }: APIContext) {\n  const markup = html(`\n  \u003Cdiv\n    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n  >\n    \u003Cdiv\n      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n    >\n      \u003Cspan\n        >Hello from\n        \u003Cspan style=\"margin-left:15ch;color: rgb(255,93,1);\"\n          >Astro\u003C\u002Fspan\n        >\u003C\u002Fspan\n      >\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>`);\n\n  const fontFilePath = `${process.cwd()}\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf`;\n\n  const fontFile = readFileSync(fontFilePath);\n\n  \u002F\u002F resulting svg is basically a long string\n  const svg: string = await satori(markup, {\n    width: 1200,\n    height: 630,\n    fonts: [\n      {\n        name: \"Optimistic Display\",\n        data: fontFile,\n        style: \"normal\",\n      },\n    ],\n  });\n\n  return new Response(svg, {\n    status: 200,\n    headers: {\n      \"Content-Type\": \"text\u002Fplain\",\n    },\n  });\n};\n",[23,726,727,741,753,765,779,783,805,819,823,827,831,835,839,843,847,851,855,859,863,867,871,877,881,903,907,919,923,929,948,956,964,968,972,980,984,992,996,1000,1005,1010,1025,1036,1042,1056,1062,1067],{"__ignoreMap":76},[80,728,729,731,733,735,737,739],{"class":82,"line":83},[80,730,147],{"class":146},[80,732,150],{"class":146},[80,734,154],{"class":153},[80,736,157],{"class":146},[80,738,160],{"class":90},[80,740,163],{"class":153},[80,742,743,745,747,749,751],{"class":82,"line":166},[80,744,147],{"class":146},[80,746,236],{"class":153},[80,748,157],{"class":146},[80,750,241],{"class":90},[80,752,163],{"class":153},[80,754,755,757,759,761,763],{"class":82,"line":173},[80,756,147],{"class":146},[80,758,437],{"class":153},[80,760,157],{"class":146},[80,762,442],{"class":90},[80,764,163],{"class":153},[80,766,767,769,772,774,777],{"class":82,"line":250},[80,768,147],{"class":146},[80,770,771],{"class":153}," satori ",[80,773,157],{"class":146},[80,775,776],{"class":90}," \"satori\"",[80,778,163],{"class":153},[80,780,781],{"class":82,"line":274},[80,782,170],{"emptyLinePlaceholder":169},[80,784,785,787,789,791,793,795,797,799,801,803],{"class":82,"line":296},[80,786,176],{"class":146},[80,788,179],{"class":146},[80,790,182],{"class":146},[80,792,185],{"class":86},[80,794,188],{"class":153},[80,796,192],{"class":191},[80,798,195],{"class":153},[80,800,198],{"class":146},[80,802,201],{"class":86},[80,804,271],{"class":153},[80,806,807,809,811,813,815,817],{"class":82,"line":302},[80,808,277],{"class":146},[80,810,281],{"class":280},[80,812,284],{"class":146},[80,814,287],{"class":86},[80,816,290],{"class":153},[80,818,293],{"class":90},[80,820,821],{"class":82,"line":308},[80,822,299],{"class":90},[80,824,825],{"class":82,"line":314},[80,826,305],{"class":90},[80,828,829],{"class":82,"line":320},[80,830,311],{"class":90},[80,832,833],{"class":82,"line":326},[80,834,317],{"class":90},[80,836,837],{"class":82,"line":332},[80,838,323],{"class":90},[80,840,841],{"class":82,"line":338},[80,842,329],{"class":90},[80,844,845],{"class":82,"line":344},[80,846,335],{"class":90},[80,848,849],{"class":82,"line":350},[80,850,341],{"class":90},[80,852,853],{"class":82,"line":356},[80,854,347],{"class":90},[80,856,857],{"class":82,"line":362},[80,858,353],{"class":90},[80,860,861],{"class":82,"line":368},[80,862,359],{"class":90},[80,864,865],{"class":82,"line":374},[80,866,365],{"class":90},[80,868,869],{"class":82,"line":383},[80,870,371],{"class":90},[80,872,873,875],{"class":82,"line":545},[80,874,377],{"class":90},[80,876,380],{"class":153},[80,878,879],{"class":82,"line":550},[80,880,170],{"emptyLinePlaceholder":169},[80,882,883,885,887,889,891,893,895,897,899,901],{"class":82,"line":579},[80,884,277],{"class":146},[80,886,555],{"class":280},[80,888,284],{"class":146},[80,890,560],{"class":90},[80,892,563],{"class":153},[80,894,210],{"class":90},[80,896,568],{"class":86},[80,898,571],{"class":90},[80,900,574],{"class":90},[80,902,163],{"class":153},[80,904,905],{"class":82,"line":584},[80,906,170],{"emptyLinePlaceholder":169},[80,908,909,911,913,915,917],{"class":82,"line":600},[80,910,277],{"class":146},[80,912,589],{"class":280},[80,914,284],{"class":146},[80,916,594],{"class":86},[80,918,597],{"class":153},[80,920,921],{"class":82,"line":605},[80,922,170],{"emptyLinePlaceholder":169},[80,924,925],{"class":82,"line":623},[80,926,928],{"class":927},"sAwPA","  \u002F\u002F resulting svg is basically a long string\n",[80,930,931,933,935,937,940,942,944,946],{"class":82,"line":635},[80,932,277],{"class":146},[80,934,610],{"class":280},[80,936,198],{"class":146},[80,938,939],{"class":280}," string",[80,941,284],{"class":146},[80,943,615],{"class":146},[80,945,94],{"class":86},[80,947,620],{"class":153},[80,949,950,952,954],{"class":82,"line":646},[80,951,626],{"class":153},[80,953,629],{"class":280},[80,955,632],{"class":153},[80,957,958,960,962],{"class":82,"line":652},[80,959,638],{"class":153},[80,961,641],{"class":280},[80,963,632],{"class":153},[80,965,966],{"class":82,"line":658},[80,967,649],{"class":153},[80,969,970],{"class":82,"line":669},[80,971,655],{"class":153},[80,973,974,976,978],{"class":82,"line":675},[80,975,661],{"class":153},[80,977,664],{"class":90},[80,979,632],{"class":153},[80,981,982],{"class":82,"line":686},[80,983,672],{"class":153},[80,985,986,988,990],{"class":82,"line":692},[80,987,678],{"class":153},[80,989,681],{"class":90},[80,991,632],{"class":153},[80,993,994],{"class":82,"line":698},[80,995,689],{"class":153},[80,997,998],{"class":82,"line":704},[80,999,695],{"class":153},[80,1001,1003],{"class":82,"line":1002},38,[80,1004,701],{"class":153},[80,1006,1008],{"class":82,"line":1007},39,[80,1009,170],{"emptyLinePlaceholder":169},[80,1011,1013,1016,1019,1022],{"class":82,"line":1012},40,[80,1014,1015],{"class":146},"  return",[80,1017,1018],{"class":146}," new",[80,1020,1021],{"class":86}," Response",[80,1023,1024],{"class":153},"(svg, {\n",[80,1026,1028,1031,1034],{"class":82,"line":1027},41,[80,1029,1030],{"class":153},"    status: ",[80,1032,1033],{"class":280},"200",[80,1035,632],{"class":153},[80,1037,1039],{"class":82,"line":1038},42,[80,1040,1041],{"class":153},"    headers: {\n",[80,1043,1045,1048,1051,1054],{"class":82,"line":1044},43,[80,1046,1047],{"class":90},"      \"Content-Type\"",[80,1049,1050],{"class":153},": ",[80,1052,1053],{"class":90},"\"text\u002Fplain\"",[80,1055,632],{"class":153},[80,1057,1059],{"class":82,"line":1058},44,[80,1060,1061],{"class":153},"    },\n",[80,1063,1065],{"class":82,"line":1064},45,[80,1066,701],{"class":153},[80,1068,1070],{"class":82,"line":1069},46,[80,1071,386],{"class":153},[10,1073,1074,1075,1077],{},"Visit ",[23,1076,107],{}," route in the browser we get SVG as a plain text",[10,1079,1080],{},[112,1081],{"alt":1082,"dataZoomable":76,"height":1083,"loading":116,"src":1084,"width":1085},"SVG as plain text response from Astro Server Endpoint",994,"\u002Fcontent\u002Fsvg_response.png",1688,[10,1087,1088,1089,1092,1093,1096],{},"Changing the ",[23,1090,1091],{},"Content-Type"," to ",[23,1094,1095],{},"\"image\u002Fsvg+xml\""," should let the browser know the\nresponse is of type svg and will render it instead of showing it as text.",[10,1098,1099],{},[112,1100],{"alt":1082,"dataZoomable":76,"height":1101,"loading":116,"src":1102,"width":1103},1217,"\u002Fcontent\u002Fsvg_as_img.png",2176,[57,1105,1107],{"id":1106},"create-png-from-svg","Create PNG from SVG",[10,1109,1110,1111,1118],{},"To convert SVG into a PNG file we can use\n",[17,1112,1115],{"href":1113,"rel":1114},"https:\u002F\u002Fsharp.pixelplumbing.com\u002F",[21],[23,1116,1117],{},"sharp"," library.",[71,1120,1122],{"className":73,"code":1121,"language":75,"meta":76,"style":76},"npm install sharp\nnpm install -D @types\u002Fsharp\n",[23,1123,1124,1133],{"__ignoreMap":76},[80,1125,1126,1128,1130],{"class":82,"line":83},[80,1127,87],{"class":86},[80,1129,91],{"class":90},[80,1131,1132],{"class":90}," sharp\n",[80,1134,1135,1137,1139,1142],{"class":82,"line":166},[80,1136,87],{"class":86},[80,1138,91],{"class":90},[80,1140,1141],{"class":280}," -D",[80,1143,1144],{"class":90}," @types\u002Fsharp\n",[71,1146,1148],{"className":137,"code":1147,"language":139,"meta":76,"style":76},"import sharp from \"sharp\";\n\nexport async function GET({ params }: APIContext) {\n  \u002F\u002F ...\n  const png = sharp(Buffer.from(svg)).png();\n  const response = await png.toBuffer();\n};\n",[23,1149,1150,1164,1168,1190,1195,1221,1240],{"__ignoreMap":76},[80,1151,1152,1154,1157,1159,1162],{"class":82,"line":83},[80,1153,147],{"class":146},[80,1155,1156],{"class":153}," sharp ",[80,1158,157],{"class":146},[80,1160,1161],{"class":90}," \"sharp\"",[80,1163,163],{"class":153},[80,1165,1166],{"class":82,"line":166},[80,1167,170],{"emptyLinePlaceholder":169},[80,1169,1170,1172,1174,1176,1178,1180,1182,1184,1186,1188],{"class":82,"line":173},[80,1171,176],{"class":146},[80,1173,179],{"class":146},[80,1175,182],{"class":146},[80,1177,185],{"class":86},[80,1179,188],{"class":153},[80,1181,192],{"class":191},[80,1183,195],{"class":153},[80,1185,198],{"class":146},[80,1187,201],{"class":86},[80,1189,271],{"class":153},[80,1191,1192],{"class":82,"line":250},[80,1193,1194],{"class":927},"  \u002F\u002F ...\n",[80,1196,1197,1199,1202,1204,1207,1210,1212,1215,1218],{"class":82,"line":274},[80,1198,277],{"class":146},[80,1200,1201],{"class":280}," png",[80,1203,284],{"class":146},[80,1205,1206],{"class":86}," sharp",[80,1208,1209],{"class":153},"(Buffer.",[80,1211,157],{"class":86},[80,1213,1214],{"class":153},"(svg)).",[80,1216,1217],{"class":86},"png",[80,1219,1220],{"class":153},"();\n",[80,1222,1223,1225,1228,1230,1232,1235,1238],{"class":82,"line":296},[80,1224,277],{"class":146},[80,1226,1227],{"class":280}," response",[80,1229,284],{"class":146},[80,1231,615],{"class":146},[80,1233,1234],{"class":153}," png.",[80,1236,1237],{"class":86},"toBuffer",[80,1239,1220],{"class":153},[80,1241,1242],{"class":82,"line":302},[80,1243,386],{"class":153},[10,1245,1246,1247,1092,1249,1252],{},"Set the ",[23,1248,1091],{},[23,1250,1251],{},"image\u002Fpng"," and return the response",[71,1254,1256],{"className":137,"code":1255,"language":139,"meta":76,"style":76},"import sharp from \"sharp\";\n\nexport async function GET({ params }: APIContext) {\n  \u002F\u002F ...\n  const png = sharp(Buffer.from(svg)).png();\n  const response = await png.toBuffer();\n  return new Response(response, {\n    status: 200,\n    headers: {\n      \"Content-Type\": \"image\u002Fpng\",\n    },\n  });\n};\n",[23,1257,1258,1270,1274,1296,1300,1320,1336,1347,1355,1359,1370,1374,1378],{"__ignoreMap":76},[80,1259,1260,1262,1264,1266,1268],{"class":82,"line":83},[80,1261,147],{"class":146},[80,1263,1156],{"class":153},[80,1265,157],{"class":146},[80,1267,1161],{"class":90},[80,1269,163],{"class":153},[80,1271,1272],{"class":82,"line":166},[80,1273,170],{"emptyLinePlaceholder":169},[80,1275,1276,1278,1280,1282,1284,1286,1288,1290,1292,1294],{"class":82,"line":173},[80,1277,176],{"class":146},[80,1279,179],{"class":146},[80,1281,182],{"class":146},[80,1283,185],{"class":86},[80,1285,188],{"class":153},[80,1287,192],{"class":191},[80,1289,195],{"class":153},[80,1291,198],{"class":146},[80,1293,201],{"class":86},[80,1295,271],{"class":153},[80,1297,1298],{"class":82,"line":250},[80,1299,1194],{"class":927},[80,1301,1302,1304,1306,1308,1310,1312,1314,1316,1318],{"class":82,"line":274},[80,1303,277],{"class":146},[80,1305,1201],{"class":280},[80,1307,284],{"class":146},[80,1309,1206],{"class":86},[80,1311,1209],{"class":153},[80,1313,157],{"class":86},[80,1315,1214],{"class":153},[80,1317,1217],{"class":86},[80,1319,1220],{"class":153},[80,1321,1322,1324,1326,1328,1330,1332,1334],{"class":82,"line":296},[80,1323,277],{"class":146},[80,1325,1227],{"class":280},[80,1327,284],{"class":146},[80,1329,615],{"class":146},[80,1331,1234],{"class":153},[80,1333,1237],{"class":86},[80,1335,1220],{"class":153},[80,1337,1338,1340,1342,1344],{"class":82,"line":302},[80,1339,1015],{"class":146},[80,1341,1018],{"class":146},[80,1343,1021],{"class":86},[80,1345,1346],{"class":153},"(response, {\n",[80,1348,1349,1351,1353],{"class":82,"line":308},[80,1350,1030],{"class":153},[80,1352,1033],{"class":280},[80,1354,632],{"class":153},[80,1356,1357],{"class":82,"line":314},[80,1358,1041],{"class":153},[80,1360,1361,1363,1365,1368],{"class":82,"line":320},[80,1362,1047],{"class":90},[80,1364,1050],{"class":153},[80,1366,1367],{"class":90},"\"image\u002Fpng\"",[80,1369,632],{"class":153},[80,1371,1372],{"class":82,"line":326},[80,1373,1061],{"class":153},[80,1375,1376],{"class":82,"line":332},[80,1377,701],{"class":153},[80,1379,1380],{"class":82,"line":338},[80,1381,386],{"class":153},[10,1383,1384,1385,1387],{},"If we check our ",[23,1386,107],{}," route now, we get the response as a image",[10,1389,1390,1396],{},[112,1391],{"alt":1392,"dataZoomable":76,"height":1393,"loading":116,"src":1394,"width":1395},"SVG converted to Png using sharp",1716,"\u002Fcontent\u002Fpng_response.png",3078,[112,1397],{"alt":1398,"dataZoomable":76,"height":1399,"loading":116,"src":1400,"width":1401},"SVG converted to Png response type in browser console",834,"\u002Fcontent\u002Fpng_response_zoomed.png",1470,[57,1403,1405],{"id":1404},"deploy","Deploy",[10,1407,1408,1409,198],{},"Astro Server Endpoints work only in SSR mode. Let's enable deployment in SSR\nmode and add an adapter by modifying ",[23,1410,1411],{},"astro.config.mjs",[71,1413,1415],{"className":137,"code":1414,"language":139,"meta":76,"style":76},"import { defineConfig } from \"astro\u002Fconfig\";\n\n\u002F\u002F https:\u002F\u002Fastro.build\u002Fconfig\nimport vercel from \"@astrojs\u002Fvercel\u002Fserverless\";\n\n\u002F\u002F https:\u002F\u002Fastro.build\u002Fconfig\nexport default defineConfig({\n  output: \"server\",\n  adapter: vercel({\n    includeFiles: [\".\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf\"],\n  }),\n});\n",[23,1416,1417,1431,1435,1440,1454,1458,1462,1475,1485,1495,1506,1511],{"__ignoreMap":76},[80,1418,1419,1421,1424,1426,1429],{"class":82,"line":83},[80,1420,147],{"class":146},[80,1422,1423],{"class":153}," { defineConfig } ",[80,1425,157],{"class":146},[80,1427,1428],{"class":90}," \"astro\u002Fconfig\"",[80,1430,163],{"class":153},[80,1432,1433],{"class":82,"line":166},[80,1434,170],{"emptyLinePlaceholder":169},[80,1436,1437],{"class":82,"line":173},[80,1438,1439],{"class":927},"\u002F\u002F https:\u002F\u002Fastro.build\u002Fconfig\n",[80,1441,1442,1444,1447,1449,1452],{"class":82,"line":250},[80,1443,147],{"class":146},[80,1445,1446],{"class":153}," vercel ",[80,1448,157],{"class":146},[80,1450,1451],{"class":90}," \"@astrojs\u002Fvercel\u002Fserverless\"",[80,1453,163],{"class":153},[80,1455,1456],{"class":82,"line":274},[80,1457,170],{"emptyLinePlaceholder":169},[80,1459,1460],{"class":82,"line":296},[80,1461,1439],{"class":927},[80,1463,1464,1466,1469,1472],{"class":82,"line":302},[80,1465,176],{"class":146},[80,1467,1468],{"class":146}," default",[80,1470,1471],{"class":86}," defineConfig",[80,1473,1474],{"class":153},"({\n",[80,1476,1477,1480,1483],{"class":82,"line":308},[80,1478,1479],{"class":153},"  output: ",[80,1481,1482],{"class":90},"\"server\"",[80,1484,632],{"class":153},[80,1486,1487,1490,1493],{"class":82,"line":314},[80,1488,1489],{"class":153},"  adapter: ",[80,1491,1492],{"class":86},"vercel",[80,1494,1474],{"class":153},[80,1496,1497,1500,1503],{"class":82,"line":320},[80,1498,1499],{"class":153},"    includeFiles: [",[80,1501,1502],{"class":90},"\".\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf\"",[80,1504,1505],{"class":153},"],\n",[80,1507,1508],{"class":82,"line":326},[80,1509,1510],{"class":153},"  }),\n",[80,1512,1513],{"class":82,"line":332},[80,1514,1515],{"class":153},"});\n",[10,1517,1518,1519,1524],{},"I'm using vercel ssr adapter here to deploy this project as a Vercel serverless\nfunction. You can check out more information about it\n",[17,1520,1523],{"href":1521,"rel":1522},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fguides\u002Fserver-side-rendering\u002F#enabling-ssr-in-your-project",[21],"here",".\nMake sure you also install the adapter package",[71,1526,1528],{"className":73,"code":1527,"language":75,"meta":76,"style":76},"npm install @astrojs\u002Fvercel\n",[23,1529,1530],{"__ignoreMap":76},[80,1531,1532,1534,1536],{"class":82,"line":83},[80,1533,87],{"class":86},[80,1535,91],{"class":90},[80,1537,1538],{"class":90}," @astrojs\u002Fvercel\n",[10,1540,1541,1542,1547],{},"I'll use ",[17,1543,1546],{"href":1544,"rel":1545},"https:\u002F\u002Fvercel.com\u002Fdocs\u002Fcli",[21],"Vercel CLI"," to deploy this project. It's\na single command to deploy the project and get a preview link. Run this in the\nroot of the current project.",[71,1549,1551],{"className":73,"code":1550,"language":75,"meta":76,"style":76},"vercel\n",[23,1552,1553],{"__ignoreMap":76},[80,1554,1555],{"class":82,"line":83},[80,1556,1550],{"class":86},[10,1558,1559],{},[112,1560],{"alt":1561,"dataZoomable":76,"height":1562,"loading":116,"src":1563,"width":1564},"Deploy this using Vercel CLI",184,"\u002Fcontent\u002Fvercel_cli.png",1512,[10,1566,1567,1568,1570,1571,1575],{},"On visiting the preview link with ",[23,1569,107],{}," path\n(",[17,1572,1573],{"href":1573,"rel":1574},"https:\u002F\u002Fastro-vercel-og-rumaan.vercel.app\u002Findex.og",[21],") I get the same OG image as\nbefore. Nice!",[57,1577,1579],{"id":1578},"update-meta-tags","Update Meta Tags",[10,1581,1582,1583,1586,1587,1590],{},"Now let's set this image link as our ",[23,1584,1585],{},"og:image"," in the site metadata. In my\n",[23,1588,1589],{},"index.astro"," file:",[71,1592,1596],{"className":1593,"code":1594,"language":1595,"meta":76,"style":76},"language-astro shiki shiki-themes github-dark","\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta http-equiv=\"x-ua-compatible\" content=\"ie=edge\" \u002F>\n    \u003Cmeta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n    \u002F>\n    \u003Cmeta name=\"theme-color\" content=\"#171C1A\" \u002F>\n    \u003Cmeta name=\"color-scheme\" content=\"dark\" \u002F>\n\n    \u003Cmeta\n      property=\"og:image\"\n      content=\"https:\u002F\u002Fastro-vercel-og.vercel.app\u002Findex.og\"\n    \u002F>\n    \u003Cmeta property=\"og:image:width\" content=\"1200\" \u002F>\n    \u003Cmeta property=\"og:image:height\" content=\"630\" \u002F>\n\n    \u003Ctitle>Vercel Astro\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Ch1>\n      Vercel Astro OG: \u003Ca href=\"\u002Findex.og\">Check here\u003C\u002Fa>\n    \u003C\u002Fh1>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","astro",[23,1597,1598,1619,1629,1648,1672,1679,1689,1699,1704,1727,1749,1753,1759,1769,1778,1782,1805,1827,1831,1845,1854,1863,1872,1894,1903,1911],{"__ignoreMap":76},[80,1599,1600,1603,1607,1610,1613,1616],{"class":82,"line":83},[80,1601,1602],{"class":153},"\u003C",[80,1604,1606],{"class":1605},"s4JwU","html",[80,1608,1609],{"class":86}," lang",[80,1611,1612],{"class":153},"=",[80,1614,1615],{"class":90},"\"en\"",[80,1617,1618],{"class":153},">\n",[80,1620,1621,1624,1627],{"class":82,"line":166},[80,1622,1623],{"class":153},"  \u003C",[80,1625,1626],{"class":1605},"head",[80,1628,1618],{"class":153},[80,1630,1631,1634,1637,1640,1642,1645],{"class":82,"line":173},[80,1632,1633],{"class":153},"    \u003C",[80,1635,1636],{"class":1605},"meta",[80,1638,1639],{"class":86}," charset",[80,1641,1612],{"class":153},[80,1643,1644],{"class":90},"\"UTF-8\"",[80,1646,1647],{"class":153}," \u002F>\n",[80,1649,1650,1652,1654,1657,1659,1662,1665,1667,1670],{"class":82,"line":250},[80,1651,1633],{"class":153},[80,1653,1636],{"class":1605},[80,1655,1656],{"class":86}," http-equiv",[80,1658,1612],{"class":153},[80,1660,1661],{"class":90},"\"x-ua-compatible\"",[80,1663,1664],{"class":86}," content",[80,1666,1612],{"class":153},[80,1668,1669],{"class":90},"\"ie=edge\"",[80,1671,1647],{"class":153},[80,1673,1674,1676],{"class":82,"line":274},[80,1675,1633],{"class":153},[80,1677,1678],{"class":1605},"meta\n",[80,1680,1681,1684,1686],{"class":82,"line":296},[80,1682,1683],{"class":86},"      name",[80,1685,1612],{"class":153},[80,1687,1688],{"class":90},"\"viewport\"\n",[80,1690,1691,1694,1696],{"class":82,"line":302},[80,1692,1693],{"class":86},"      content",[80,1695,1612],{"class":153},[80,1697,1698],{"class":90},"\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n",[80,1700,1701],{"class":82,"line":308},[80,1702,1703],{"class":153},"    \u002F>\n",[80,1705,1706,1708,1710,1713,1715,1718,1720,1722,1725],{"class":82,"line":314},[80,1707,1633],{"class":153},[80,1709,1636],{"class":1605},[80,1711,1712],{"class":86}," name",[80,1714,1612],{"class":153},[80,1716,1717],{"class":90},"\"theme-color\"",[80,1719,1664],{"class":86},[80,1721,1612],{"class":153},[80,1723,1724],{"class":90},"\"#171C1A\"",[80,1726,1647],{"class":153},[80,1728,1729,1731,1733,1735,1737,1740,1742,1744,1747],{"class":82,"line":320},[80,1730,1633],{"class":153},[80,1732,1636],{"class":1605},[80,1734,1712],{"class":86},[80,1736,1612],{"class":153},[80,1738,1739],{"class":90},"\"color-scheme\"",[80,1741,1664],{"class":86},[80,1743,1612],{"class":153},[80,1745,1746],{"class":90},"\"dark\"",[80,1748,1647],{"class":153},[80,1750,1751],{"class":82,"line":326},[80,1752,170],{"emptyLinePlaceholder":169},[80,1754,1755,1757],{"class":82,"line":332},[80,1756,1633],{"class":153},[80,1758,1678],{"class":1605},[80,1760,1761,1764,1766],{"class":82,"line":338},[80,1762,1763],{"class":86},"      property",[80,1765,1612],{"class":153},[80,1767,1768],{"class":90},"\"og:image\"\n",[80,1770,1771,1773,1775],{"class":82,"line":344},[80,1772,1693],{"class":86},[80,1774,1612],{"class":153},[80,1776,1777],{"class":90},"\"https:\u002F\u002Fastro-vercel-og.vercel.app\u002Findex.og\"\n",[80,1779,1780],{"class":82,"line":350},[80,1781,1703],{"class":153},[80,1783,1784,1786,1788,1791,1793,1796,1798,1800,1803],{"class":82,"line":356},[80,1785,1633],{"class":153},[80,1787,1636],{"class":1605},[80,1789,1790],{"class":86}," property",[80,1792,1612],{"class":153},[80,1794,1795],{"class":90},"\"og:image:width\"",[80,1797,1664],{"class":86},[80,1799,1612],{"class":153},[80,1801,1802],{"class":90},"\"1200\"",[80,1804,1647],{"class":153},[80,1806,1807,1809,1811,1813,1815,1818,1820,1822,1825],{"class":82,"line":362},[80,1808,1633],{"class":153},[80,1810,1636],{"class":1605},[80,1812,1790],{"class":86},[80,1814,1612],{"class":153},[80,1816,1817],{"class":90},"\"og:image:height\"",[80,1819,1664],{"class":86},[80,1821,1612],{"class":153},[80,1823,1824],{"class":90},"\"630\"",[80,1826,1647],{"class":153},[80,1828,1829],{"class":82,"line":368},[80,1830,170],{"emptyLinePlaceholder":169},[80,1832,1833,1835,1838,1841,1843],{"class":82,"line":374},[80,1834,1633],{"class":153},[80,1836,1837],{"class":1605},"title",[80,1839,1840],{"class":153},">Vercel Astro\u003C\u002F",[80,1842,1837],{"class":1605},[80,1844,1618],{"class":153},[80,1846,1847,1850,1852],{"class":82,"line":383},[80,1848,1849],{"class":153},"  \u003C\u002F",[80,1851,1626],{"class":1605},[80,1853,1618],{"class":153},[80,1855,1856,1858,1861],{"class":82,"line":545},[80,1857,1623],{"class":153},[80,1859,1860],{"class":1605},"body",[80,1862,1618],{"class":153},[80,1864,1865,1867,1870],{"class":82,"line":550},[80,1866,1633],{"class":153},[80,1868,1869],{"class":1605},"h1",[80,1871,1618],{"class":153},[80,1873,1874,1877,1879,1882,1884,1887,1890,1892],{"class":82,"line":579},[80,1875,1876],{"class":153},"      Vercel Astro OG: \u003C",[80,1878,17],{"class":1605},[80,1880,1881],{"class":86}," href",[80,1883,1612],{"class":153},[80,1885,1886],{"class":90},"\"\u002Findex.og\"",[80,1888,1889],{"class":153},">Check here\u003C\u002F",[80,1891,17],{"class":1605},[80,1893,1618],{"class":153},[80,1895,1896,1899,1901],{"class":82,"line":584},[80,1897,1898],{"class":153},"    \u003C\u002F",[80,1900,1869],{"class":1605},[80,1902,1618],{"class":153},[80,1904,1905,1907,1909],{"class":82,"line":600},[80,1906,1849],{"class":153},[80,1908,1860],{"class":1605},[80,1910,1618],{"class":153},[80,1912,1913,1916,1918],{"class":82,"line":605},[80,1914,1915],{"class":153},"\u003C\u002F",[80,1917,1606],{"class":1605},[80,1919,1618],{"class":153},[10,1921,1922],{},"This will help crawlers show the generated OG image while sharing links on their\nplatform. We can debug this by using services like:",[1924,1925,1926,1934],"ul",{},[1927,1928,1929],"li",{},[17,1930,1933],{"href":1931,"rel":1932},"https:\u002F\u002Fdevelopers.facebook.com\u002Ftools\u002Fdebug\u002F",[21],"Facebook Share Debugger",[1927,1935,1936],{},[17,1937,1940],{"href":1938,"rel":1939},"https:\u002F\u002Fwww.opengraph.xyz\u002F",[21],"OpenGraph.xyz",[57,1942,1944],{"id":1943},"generate-dynamic-og-images","Generate Dynamic OG Images",[10,1946,1947,1948,1955],{},"Astro Server Endpoints give access to the\n",[17,1949,1952],{"href":1950,"rel":1951},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fcore-concepts\u002Fendpoints\u002F#request",[21],[23,1953,1954],{},"Request"," Object\nwhich contains query and path params using which we can generate dynamic open\ngraph images. Let's say we had a bunch of blog post articles (.md) and we want\nto generate OG images containing the blog post title.",[10,1957,1958,1959,66,1962,1965,1966,1969],{},"I've created two markdown files ",[23,1960,1961],{},"post1.md",[23,1963,1964],{},"post2.md"," under ",[23,1967,1968],{},"\u002Fpages\u002Fblog\u002F","\ndirectory with a simple frontmatter containing a title and a reference to the\nlayout file.",[71,1971,1975],{"className":1972,"code":1973,"language":1974,"meta":76,"style":76},"language-md shiki shiki-themes github-dark","---\ntitle: \"Hello World\"\nlayout: \"..\u002F..\u002Flayouts\u002FBlogLayout.astro\"\n---\n\nHello World content\n","md",[23,1976,1977,1983,1988,1993,1997,2001],{"__ignoreMap":76},[80,1978,1979],{"class":82,"line":83},[80,1980,1982],{"class":1981},"sIZOC","---\n",[80,1984,1985],{"class":82,"line":166},[80,1986,1987],{"class":153},"title: \"Hello World\"\n",[80,1989,1990],{"class":82,"line":173},[80,1991,1992],{"class":153},"layout: \"..\u002F..\u002Flayouts\u002FBlogLayout.astro\"\n",[80,1994,1995],{"class":82,"line":250},[80,1996,1982],{"class":1981},[80,1998,1999],{"class":82,"line":274},[80,2000,170],{"emptyLinePlaceholder":169},[80,2002,2003],{"class":82,"line":296},[80,2004,2005],{"class":153},"Hello World content\n",[10,2007,2008],{},[112,2009],{"alt":2010,"dataZoomable":76,"height":2011,"loading":116,"src":2012,"width":2013},"Simple Blog Directory Setup with Markdown Files",638,"\u002Fcontent\u002Fblog_directory.png",468,[10,2015,2016,2017,1590],{},"The ",[23,2018,2019],{},"layouts\u002FBlogLayout.astro",[71,2021,2023],{"className":1593,"code":2022,"language":1595,"meta":76,"style":76},"---\nconst { frontmatter } = Astro.props;\n---\n\n\u003Cmain>\n  \u003Ch1>{frontmatter.title}\u003C\u002Fh1>\n  \u003Cslot \u002F>\n\u003C\u002Fmain>\n",[23,2024,2025,2029,2048,2052,2056,2065,2078,2087],{"__ignoreMap":76},[80,2026,2027],{"class":82,"line":83},[80,2028,1982],{"class":927},[80,2030,2031,2034,2037,2040,2043,2045],{"class":82,"line":166},[80,2032,2033],{"class":146},"const",[80,2035,2036],{"class":153}," { ",[80,2038,2039],{"class":280},"frontmatter",[80,2041,2042],{"class":153}," } ",[80,2044,1612],{"class":146},[80,2046,2047],{"class":153}," Astro.props;\n",[80,2049,2050],{"class":82,"line":173},[80,2051,1982],{"class":927},[80,2053,2054],{"class":82,"line":250},[80,2055,170],{"emptyLinePlaceholder":169},[80,2057,2058,2060,2063],{"class":82,"line":274},[80,2059,1602],{"class":153},[80,2061,2062],{"class":1605},"main",[80,2064,1618],{"class":153},[80,2066,2067,2069,2071,2074,2076],{"class":82,"line":296},[80,2068,1623],{"class":153},[80,2070,1869],{"class":1605},[80,2072,2073],{"class":153},">{frontmatter.title}\u003C\u002F",[80,2075,1869],{"class":1605},[80,2077,1618],{"class":153},[80,2079,2080,2082,2085],{"class":82,"line":302},[80,2081,1623],{"class":153},[80,2083,2084],{"class":1605},"slot",[80,2086,1647],{"class":153},[80,2088,2089,2091,2093],{"class":82,"line":308},[80,2090,1915],{"class":153},[80,2092,2062],{"class":1605},[80,2094,1618],{"class":153},[10,2096,2097,2098,2103,2104,2107,2108,2110,2111,2113],{},"Now let's create a server endpoint file (I'll call it [slug].og.ts) which is\nalso a\n",[17,2099,2102],{"href":2100,"rel":2101},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fcore-concepts\u002Frouting\u002F#dynamic-routes",[21],"Dynamic Route","\nwith path param called ",[23,2105,2106],{},"slug"," which can be later referenced in our code. We'll\ncreate a similar GET route and access the ",[23,2109,2106],{}," path param from the ",[23,2112,1954],{},"\nobject.",[10,2115,2116,1590],{},[23,2117,2118],{},"[slug].og.ts",[71,2120,2122],{"className":137,"code":2121,"language":139,"meta":76,"style":76},"export async function GET({ params }: APIContext) {\n  const { slug } = params;\n};\n",[23,2123,2124,2146,2161],{"__ignoreMap":76},[80,2125,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144],{"class":82,"line":83},[80,2127,176],{"class":146},[80,2129,179],{"class":146},[80,2131,182],{"class":146},[80,2133,185],{"class":86},[80,2135,188],{"class":153},[80,2137,192],{"class":191},[80,2139,195],{"class":153},[80,2141,198],{"class":146},[80,2143,201],{"class":86},[80,2145,271],{"class":153},[80,2147,2148,2150,2152,2154,2156,2158],{"class":82,"line":166},[80,2149,277],{"class":146},[80,2151,2036],{"class":153},[80,2153,2106],{"class":280},[80,2155,2042],{"class":153},[80,2157,1612],{"class":146},[80,2159,2160],{"class":153}," params;\n",[80,2162,2163],{"class":82,"line":173},[80,2164,386],{"class":153},[10,2166,2167,2168,2170,2171,2173,2174,2181],{},"Using the ",[23,2169,2106],{}," we'll find the corresponding Blog post markdown file and access\nit's frontmatter that contains the ",[23,2172,1837],{}," and use that in our html template for\ngenerating og image. I'll use\n",[17,2175,2178],{"href":2176,"rel":2177},"https:\u002F\u002Fvitejs.dev\u002Fguide\u002Fmigration.html#import-meta-glob",[21],[23,2179,2180],{},"import.meta.glob","\nfrom Vite to get all the markdown files in a directory, find the markdown file\nmatching slug, load that markdown file and get the post title.",[71,2183,2185],{"className":137,"code":2184,"language":139,"meta":76,"style":76},"\u002F\u002F other imports...\nimport { readFileSync } from \"fs\";\nimport { basename } from \"path\";\nimport type { APIContext, MarkdownInstance } from \"astro\";\n\nexport async function GET({ params }: APIContext) {\n  const { slug } = params;\n  \u002F\u002F Find the slug in content dir\n  const posts: Record\u003Cstring, () => Promise\u003Cany>> = import.meta.glob(\n    `.\u002F**\u002F*.md`\n  );\n\n  const postPaths = Object.entries(posts).map(([path, promise]) => ({\n    slug: basename(path).replace(\".md\", \"\"),\n    loadPost: promise,\n  }));\n\n  const post = postPaths.find((p) => p.slug === String(slug));\n  let postTitle = `My Blog`; \u002F\u002F Default title if post not found\n  if (post) {\n    const postData = (await post.loadPost()) as MarkdownInstance\u003C\n      Record\u003Cstring, any>\n    >;\n    postTitle = postData.frontmatter.title;\n  }\n  \u002F\u002F ...\n};\n",[23,2186,2187,2192,2204,2218,2233,2237,2259,2273,2278,2329,2334,2339,2343,2384,2411,2416,2421,2425,2462,2481,2489,2523,2538,2543,2553,2558,2562],{"__ignoreMap":76},[80,2188,2189],{"class":82,"line":83},[80,2190,2191],{"class":927},"\u002F\u002F other imports...\n",[80,2193,2194,2196,2198,2200,2202],{"class":82,"line":166},[80,2195,147],{"class":146},[80,2197,437],{"class":153},[80,2199,157],{"class":146},[80,2201,442],{"class":90},[80,2203,163],{"class":153},[80,2205,2206,2208,2211,2213,2216],{"class":82,"line":173},[80,2207,147],{"class":146},[80,2209,2210],{"class":153}," { basename } ",[80,2212,157],{"class":146},[80,2214,2215],{"class":90}," \"path\"",[80,2217,163],{"class":153},[80,2219,2220,2222,2224,2227,2229,2231],{"class":82,"line":250},[80,2221,147],{"class":146},[80,2223,150],{"class":146},[80,2225,2226],{"class":153}," { APIContext, MarkdownInstance } ",[80,2228,157],{"class":146},[80,2230,160],{"class":90},[80,2232,163],{"class":153},[80,2234,2235],{"class":82,"line":274},[80,2236,170],{"emptyLinePlaceholder":169},[80,2238,2239,2241,2243,2245,2247,2249,2251,2253,2255,2257],{"class":82,"line":296},[80,2240,176],{"class":146},[80,2242,179],{"class":146},[80,2244,182],{"class":146},[80,2246,185],{"class":86},[80,2248,188],{"class":153},[80,2250,192],{"class":191},[80,2252,195],{"class":153},[80,2254,198],{"class":146},[80,2256,201],{"class":86},[80,2258,271],{"class":153},[80,2260,2261,2263,2265,2267,2269,2271],{"class":82,"line":302},[80,2262,277],{"class":146},[80,2264,2036],{"class":153},[80,2266,2106],{"class":280},[80,2268,2042],{"class":153},[80,2270,1612],{"class":146},[80,2272,2160],{"class":153},[80,2274,2275],{"class":82,"line":308},[80,2276,2277],{"class":927},"  \u002F\u002F Find the slug in content dir\n",[80,2279,2280,2282,2285,2287,2290,2292,2295,2298,2301,2304,2306,2309,2312,2314,2317,2319,2321,2323,2326],{"class":82,"line":314},[80,2281,277],{"class":146},[80,2283,2284],{"class":280}," posts",[80,2286,198],{"class":146},[80,2288,2289],{"class":86}," Record",[80,2291,1602],{"class":153},[80,2293,2294],{"class":280},"string",[80,2296,2297],{"class":153},", () ",[80,2299,2300],{"class":146},"=>",[80,2302,2303],{"class":86}," Promise",[80,2305,1602],{"class":153},[80,2307,2308],{"class":280},"any",[80,2310,2311],{"class":153},">> ",[80,2313,1612],{"class":146},[80,2315,2316],{"class":146}," import",[80,2318,210],{"class":153},[80,2320,1636],{"class":280},[80,2322,210],{"class":153},[80,2324,2325],{"class":86},"glob",[80,2327,2328],{"class":153},"(\n",[80,2330,2331],{"class":82,"line":320},[80,2332,2333],{"class":90},"    `.\u002F**\u002F*.md`\n",[80,2335,2336],{"class":82,"line":326},[80,2337,2338],{"class":153},"  );\n",[80,2340,2341],{"class":82,"line":332},[80,2342,170],{"emptyLinePlaceholder":169},[80,2344,2345,2347,2350,2352,2355,2358,2361,2364,2367,2370,2373,2376,2379,2381],{"class":82,"line":338},[80,2346,277],{"class":146},[80,2348,2349],{"class":280}," postPaths",[80,2351,284],{"class":146},[80,2353,2354],{"class":153}," Object.",[80,2356,2357],{"class":86},"entries",[80,2359,2360],{"class":153},"(posts).",[80,2362,2363],{"class":86},"map",[80,2365,2366],{"class":153},"(([",[80,2368,2369],{"class":191},"path",[80,2371,2372],{"class":153},", ",[80,2374,2375],{"class":191},"promise",[80,2377,2378],{"class":153},"]) ",[80,2380,2300],{"class":146},[80,2382,2383],{"class":153}," ({\n",[80,2385,2386,2389,2392,2395,2398,2400,2403,2405,2408],{"class":82,"line":344},[80,2387,2388],{"class":153},"    slug: ",[80,2390,2391],{"class":86},"basename",[80,2393,2394],{"class":153},"(path).",[80,2396,2397],{"class":86},"replace",[80,2399,290],{"class":153},[80,2401,2402],{"class":90},"\".md\"",[80,2404,2372],{"class":153},[80,2406,2407],{"class":90},"\"\"",[80,2409,2410],{"class":153},"),\n",[80,2412,2413],{"class":82,"line":350},[80,2414,2415],{"class":153},"    loadPost: promise,\n",[80,2417,2418],{"class":82,"line":356},[80,2419,2420],{"class":153},"  }));\n",[80,2422,2423],{"class":82,"line":362},[80,2424,170],{"emptyLinePlaceholder":169},[80,2426,2427,2429,2432,2434,2437,2440,2443,2445,2448,2450,2453,2456,2459],{"class":82,"line":368},[80,2428,277],{"class":146},[80,2430,2431],{"class":280}," post",[80,2433,284],{"class":146},[80,2435,2436],{"class":153}," postPaths.",[80,2438,2439],{"class":86},"find",[80,2441,2442],{"class":153},"((",[80,2444,10],{"class":191},[80,2446,2447],{"class":153},") ",[80,2449,2300],{"class":146},[80,2451,2452],{"class":153}," p.slug ",[80,2454,2455],{"class":146},"===",[80,2457,2458],{"class":86}," String",[80,2460,2461],{"class":153},"(slug));\n",[80,2463,2464,2467,2470,2472,2475,2478],{"class":82,"line":374},[80,2465,2466],{"class":146},"  let",[80,2468,2469],{"class":153}," postTitle ",[80,2471,1612],{"class":146},[80,2473,2474],{"class":90}," `My Blog`",[80,2476,2477],{"class":153},"; ",[80,2479,2480],{"class":927},"\u002F\u002F Default title if post not found\n",[80,2482,2483,2486],{"class":82,"line":383},[80,2484,2485],{"class":146},"  if",[80,2487,2488],{"class":153}," (post) {\n",[80,2490,2491,2494,2497,2499,2502,2505,2508,2511,2514,2517,2520],{"class":82,"line":545},[80,2492,2493],{"class":146},"    const",[80,2495,2496],{"class":280}," postData",[80,2498,284],{"class":146},[80,2500,2501],{"class":153}," (",[80,2503,2504],{"class":146},"await",[80,2506,2507],{"class":153}," post.",[80,2509,2510],{"class":86},"loadPost",[80,2512,2513],{"class":153},"()) ",[80,2515,2516],{"class":146},"as",[80,2518,2519],{"class":86}," MarkdownInstance",[80,2521,2522],{"class":153},"\u003C\n",[80,2524,2525,2528,2530,2532,2534,2536],{"class":82,"line":550},[80,2526,2527],{"class":86},"      Record",[80,2529,1602],{"class":153},[80,2531,2294],{"class":280},[80,2533,2372],{"class":153},[80,2535,2308],{"class":280},[80,2537,1618],{"class":153},[80,2539,2540],{"class":82,"line":579},[80,2541,2542],{"class":153},"    >;\n",[80,2544,2545,2548,2550],{"class":82,"line":584},[80,2546,2547],{"class":153},"    postTitle ",[80,2549,1612],{"class":146},[80,2551,2552],{"class":153}," postData.frontmatter.title;\n",[80,2554,2555],{"class":82,"line":600},[80,2556,2557],{"class":153},"  }\n",[80,2559,2560],{"class":82,"line":605},[80,2561,1194],{"class":927},[80,2563,2564],{"class":82,"line":623},[80,2565,386],{"class":153},[10,2567,2568],{},"Now that we have the post title, let's inject it into our html markup",[71,2570,2572],{"className":137,"code":2571,"language":139,"meta":76,"style":76},"import satori from \"satori\";\nimport { html } from \"satori-html\";\nimport { readFileSync } from \"fs\";\nimport type { APIContext, MarkdownInstance } from \"astro\";\nimport sharp from \"sharp\";\nimport { basename } from \"path\";\n\nexport async function GET({ params }: APIContext) {\n  const { slug } = params;\n  \u002F\u002F Find the slug in content dir\n  const posts: Record\u003Cstring, () => Promise\u003Cany>> = import.meta.glob(\n    `.\u002F**\u002F*.md`\n  );\n\n  const postPaths = Object.entries(posts).map(([path, promise]) => ({\n    slug: basename(path).replace(\".md\", \"\"),\n    loadPost: promise,\n  }));\n\n  const post = postPaths.find((p) => p.slug === String(slug));\n  let postTitle = `My Blog`; \u002F\u002F Default title if post not found\n  if (post) {\n    const postData = (await post.loadPost()) as MarkdownInstance\u003C\n      Record\u003Cstring, any>\n    >;\n    postTitle = postData.frontmatter.title;\n  }\n\n  const fontFilePath = `${process.cwd()}\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf`;\n\n  const fontFile = readFileSync(fontFilePath);\n\n  const markup = html(`\n  \u003Cdiv\n    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n  >\n    \u003Cdiv\n      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n    >\n      ${postTitle}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>`);\n  const svg = await satori(markup, {\n    width: 1200,\n    height: 630,\n    fonts: [\n      {\n        name: \"Optimistic Display\",\n        data: fontFile,\n        style: \"normal\",\n      },\n    ],\n  });\n\n  const png = sharp(Buffer.from(svg)).png();\n  const response = await png.toBuffer();\n\n  return new Response(response, {\n    status: 200,\n    headers: {\n      \"Content-Type\": \"image\u002Fpng\",\n    },\n  });\n};\n",[23,2573,2574,2586,2598,2610,2624,2636,2648,2652,2674,2688,2692,2732,2736,2740,2744,2774,2794,2798,2802,2806,2834,2848,2854,2878,2892,2896,2904,2908,2912,2934,2938,2950,2954,2968,2972,2976,2980,2984,2988,2992,3003,3007,3013,3027,3035,3043,3047,3052,3061,3066,3075,3080,3085,3090,3095,3116,3133,3138,3149,3158,3163,3174,3179,3184],{"__ignoreMap":76},[80,2575,2576,2578,2580,2582,2584],{"class":82,"line":83},[80,2577,147],{"class":146},[80,2579,771],{"class":153},[80,2581,157],{"class":146},[80,2583,776],{"class":90},[80,2585,163],{"class":153},[80,2587,2588,2590,2592,2594,2596],{"class":82,"line":166},[80,2589,147],{"class":146},[80,2591,236],{"class":153},[80,2593,157],{"class":146},[80,2595,241],{"class":90},[80,2597,163],{"class":153},[80,2599,2600,2602,2604,2606,2608],{"class":82,"line":173},[80,2601,147],{"class":146},[80,2603,437],{"class":153},[80,2605,157],{"class":146},[80,2607,442],{"class":90},[80,2609,163],{"class":153},[80,2611,2612,2614,2616,2618,2620,2622],{"class":82,"line":250},[80,2613,147],{"class":146},[80,2615,150],{"class":146},[80,2617,2226],{"class":153},[80,2619,157],{"class":146},[80,2621,160],{"class":90},[80,2623,163],{"class":153},[80,2625,2626,2628,2630,2632,2634],{"class":82,"line":274},[80,2627,147],{"class":146},[80,2629,1156],{"class":153},[80,2631,157],{"class":146},[80,2633,1161],{"class":90},[80,2635,163],{"class":153},[80,2637,2638,2640,2642,2644,2646],{"class":82,"line":296},[80,2639,147],{"class":146},[80,2641,2210],{"class":153},[80,2643,157],{"class":146},[80,2645,2215],{"class":90},[80,2647,163],{"class":153},[80,2649,2650],{"class":82,"line":302},[80,2651,170],{"emptyLinePlaceholder":169},[80,2653,2654,2656,2658,2660,2662,2664,2666,2668,2670,2672],{"class":82,"line":308},[80,2655,176],{"class":146},[80,2657,179],{"class":146},[80,2659,182],{"class":146},[80,2661,185],{"class":86},[80,2663,188],{"class":153},[80,2665,192],{"class":191},[80,2667,195],{"class":153},[80,2669,198],{"class":146},[80,2671,201],{"class":86},[80,2673,271],{"class":153},[80,2675,2676,2678,2680,2682,2684,2686],{"class":82,"line":314},[80,2677,277],{"class":146},[80,2679,2036],{"class":153},[80,2681,2106],{"class":280},[80,2683,2042],{"class":153},[80,2685,1612],{"class":146},[80,2687,2160],{"class":153},[80,2689,2690],{"class":82,"line":320},[80,2691,2277],{"class":927},[80,2693,2694,2696,2698,2700,2702,2704,2706,2708,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730],{"class":82,"line":326},[80,2695,277],{"class":146},[80,2697,2284],{"class":280},[80,2699,198],{"class":146},[80,2701,2289],{"class":86},[80,2703,1602],{"class":153},[80,2705,2294],{"class":280},[80,2707,2297],{"class":153},[80,2709,2300],{"class":146},[80,2711,2303],{"class":86},[80,2713,1602],{"class":153},[80,2715,2308],{"class":280},[80,2717,2311],{"class":153},[80,2719,1612],{"class":146},[80,2721,2316],{"class":146},[80,2723,210],{"class":153},[80,2725,1636],{"class":280},[80,2727,210],{"class":153},[80,2729,2325],{"class":86},[80,2731,2328],{"class":153},[80,2733,2734],{"class":82,"line":332},[80,2735,2333],{"class":90},[80,2737,2738],{"class":82,"line":338},[80,2739,2338],{"class":153},[80,2741,2742],{"class":82,"line":344},[80,2743,170],{"emptyLinePlaceholder":169},[80,2745,2746,2748,2750,2752,2754,2756,2758,2760,2762,2764,2766,2768,2770,2772],{"class":82,"line":350},[80,2747,277],{"class":146},[80,2749,2349],{"class":280},[80,2751,284],{"class":146},[80,2753,2354],{"class":153},[80,2755,2357],{"class":86},[80,2757,2360],{"class":153},[80,2759,2363],{"class":86},[80,2761,2366],{"class":153},[80,2763,2369],{"class":191},[80,2765,2372],{"class":153},[80,2767,2375],{"class":191},[80,2769,2378],{"class":153},[80,2771,2300],{"class":146},[80,2773,2383],{"class":153},[80,2775,2776,2778,2780,2782,2784,2786,2788,2790,2792],{"class":82,"line":356},[80,2777,2388],{"class":153},[80,2779,2391],{"class":86},[80,2781,2394],{"class":153},[80,2783,2397],{"class":86},[80,2785,290],{"class":153},[80,2787,2402],{"class":90},[80,2789,2372],{"class":153},[80,2791,2407],{"class":90},[80,2793,2410],{"class":153},[80,2795,2796],{"class":82,"line":362},[80,2797,2415],{"class":153},[80,2799,2800],{"class":82,"line":368},[80,2801,2420],{"class":153},[80,2803,2804],{"class":82,"line":374},[80,2805,170],{"emptyLinePlaceholder":169},[80,2807,2808,2810,2812,2814,2816,2818,2820,2822,2824,2826,2828,2830,2832],{"class":82,"line":383},[80,2809,277],{"class":146},[80,2811,2431],{"class":280},[80,2813,284],{"class":146},[80,2815,2436],{"class":153},[80,2817,2439],{"class":86},[80,2819,2442],{"class":153},[80,2821,10],{"class":191},[80,2823,2447],{"class":153},[80,2825,2300],{"class":146},[80,2827,2452],{"class":153},[80,2829,2455],{"class":146},[80,2831,2458],{"class":86},[80,2833,2461],{"class":153},[80,2835,2836,2838,2840,2842,2844,2846],{"class":82,"line":545},[80,2837,2466],{"class":146},[80,2839,2469],{"class":153},[80,2841,1612],{"class":146},[80,2843,2474],{"class":90},[80,2845,2477],{"class":153},[80,2847,2480],{"class":927},[80,2849,2850,2852],{"class":82,"line":550},[80,2851,2485],{"class":146},[80,2853,2488],{"class":153},[80,2855,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2876],{"class":82,"line":579},[80,2857,2493],{"class":146},[80,2859,2496],{"class":280},[80,2861,284],{"class":146},[80,2863,2501],{"class":153},[80,2865,2504],{"class":146},[80,2867,2507],{"class":153},[80,2869,2510],{"class":86},[80,2871,2513],{"class":153},[80,2873,2516],{"class":146},[80,2875,2519],{"class":86},[80,2877,2522],{"class":153},[80,2879,2880,2882,2884,2886,2888,2890],{"class":82,"line":584},[80,2881,2527],{"class":86},[80,2883,1602],{"class":153},[80,2885,2294],{"class":280},[80,2887,2372],{"class":153},[80,2889,2308],{"class":280},[80,2891,1618],{"class":153},[80,2893,2894],{"class":82,"line":600},[80,2895,2542],{"class":153},[80,2897,2898,2900,2902],{"class":82,"line":605},[80,2899,2547],{"class":153},[80,2901,1612],{"class":146},[80,2903,2552],{"class":153},[80,2905,2906],{"class":82,"line":623},[80,2907,2557],{"class":153},[80,2909,2910],{"class":82,"line":635},[80,2911,170],{"emptyLinePlaceholder":169},[80,2913,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932],{"class":82,"line":646},[80,2915,277],{"class":146},[80,2917,555],{"class":280},[80,2919,284],{"class":146},[80,2921,560],{"class":90},[80,2923,563],{"class":153},[80,2925,210],{"class":90},[80,2927,568],{"class":86},[80,2929,571],{"class":90},[80,2931,574],{"class":90},[80,2933,163],{"class":153},[80,2935,2936],{"class":82,"line":652},[80,2937,170],{"emptyLinePlaceholder":169},[80,2939,2940,2942,2944,2946,2948],{"class":82,"line":658},[80,2941,277],{"class":146},[80,2943,589],{"class":280},[80,2945,284],{"class":146},[80,2947,594],{"class":86},[80,2949,597],{"class":153},[80,2951,2952],{"class":82,"line":669},[80,2953,170],{"emptyLinePlaceholder":169},[80,2955,2956,2958,2960,2962,2964,2966],{"class":82,"line":675},[80,2957,277],{"class":146},[80,2959,281],{"class":280},[80,2961,284],{"class":146},[80,2963,287],{"class":86},[80,2965,290],{"class":153},[80,2967,293],{"class":90},[80,2969,2970],{"class":82,"line":686},[80,2971,299],{"class":90},[80,2973,2974],{"class":82,"line":692},[80,2975,305],{"class":90},[80,2977,2978],{"class":82,"line":698},[80,2979,311],{"class":90},[80,2981,2982],{"class":82,"line":704},[80,2983,317],{"class":90},[80,2985,2986],{"class":82,"line":1002},[80,2987,323],{"class":90},[80,2989,2990],{"class":82,"line":1007},[80,2991,329],{"class":90},[80,2993,2994,2997,3000],{"class":82,"line":1012},[80,2995,2996],{"class":90},"      ${",[80,2998,2999],{"class":153},"postTitle",[80,3001,3002],{"class":90},"}\n",[80,3004,3005],{"class":82,"line":1027},[80,3006,371],{"class":90},[80,3008,3009,3011],{"class":82,"line":1038},[80,3010,377],{"class":90},[80,3012,380],{"class":153},[80,3014,3015,3017,3019,3021,3023,3025],{"class":82,"line":1044},[80,3016,277],{"class":146},[80,3018,610],{"class":280},[80,3020,284],{"class":146},[80,3022,615],{"class":146},[80,3024,94],{"class":86},[80,3026,620],{"class":153},[80,3028,3029,3031,3033],{"class":82,"line":1058},[80,3030,626],{"class":153},[80,3032,629],{"class":280},[80,3034,632],{"class":153},[80,3036,3037,3039,3041],{"class":82,"line":1064},[80,3038,638],{"class":153},[80,3040,641],{"class":280},[80,3042,632],{"class":153},[80,3044,3045],{"class":82,"line":1069},[80,3046,649],{"class":153},[80,3048,3050],{"class":82,"line":3049},47,[80,3051,655],{"class":153},[80,3053,3055,3057,3059],{"class":82,"line":3054},48,[80,3056,661],{"class":153},[80,3058,664],{"class":90},[80,3060,632],{"class":153},[80,3062,3064],{"class":82,"line":3063},49,[80,3065,672],{"class":153},[80,3067,3069,3071,3073],{"class":82,"line":3068},50,[80,3070,678],{"class":153},[80,3072,681],{"class":90},[80,3074,632],{"class":153},[80,3076,3078],{"class":82,"line":3077},51,[80,3079,689],{"class":153},[80,3081,3083],{"class":82,"line":3082},52,[80,3084,695],{"class":153},[80,3086,3088],{"class":82,"line":3087},53,[80,3089,701],{"class":153},[80,3091,3093],{"class":82,"line":3092},54,[80,3094,170],{"emptyLinePlaceholder":169},[80,3096,3098,3100,3102,3104,3106,3108,3110,3112,3114],{"class":82,"line":3097},55,[80,3099,277],{"class":146},[80,3101,1201],{"class":280},[80,3103,284],{"class":146},[80,3105,1206],{"class":86},[80,3107,1209],{"class":153},[80,3109,157],{"class":86},[80,3111,1214],{"class":153},[80,3113,1217],{"class":86},[80,3115,1220],{"class":153},[80,3117,3119,3121,3123,3125,3127,3129,3131],{"class":82,"line":3118},56,[80,3120,277],{"class":146},[80,3122,1227],{"class":280},[80,3124,284],{"class":146},[80,3126,615],{"class":146},[80,3128,1234],{"class":153},[80,3130,1237],{"class":86},[80,3132,1220],{"class":153},[80,3134,3136],{"class":82,"line":3135},57,[80,3137,170],{"emptyLinePlaceholder":169},[80,3139,3141,3143,3145,3147],{"class":82,"line":3140},58,[80,3142,1015],{"class":146},[80,3144,1018],{"class":146},[80,3146,1021],{"class":86},[80,3148,1346],{"class":153},[80,3150,3152,3154,3156],{"class":82,"line":3151},59,[80,3153,1030],{"class":153},[80,3155,1033],{"class":280},[80,3157,632],{"class":153},[80,3159,3161],{"class":82,"line":3160},60,[80,3162,1041],{"class":153},[80,3164,3166,3168,3170,3172],{"class":82,"line":3165},61,[80,3167,1047],{"class":90},[80,3169,1050],{"class":153},[80,3171,1367],{"class":90},[80,3173,632],{"class":153},[80,3175,3177],{"class":82,"line":3176},62,[80,3178,1061],{"class":153},[80,3180,3182],{"class":82,"line":3181},63,[80,3183,701],{"class":153},[80,3185,3187],{"class":82,"line":3186},64,[80,3188,386],{"class":153},[10,3190,3191,3192,3195,3196,198],{},"If we visit ",[23,3193,3194],{},"\u002Fblog\u002F[slug].og"," path we should get an image with the corresponding\nblog post title in it. Let's try ",[23,3197,3198],{},"\u002Fblog\u002Fpost1.og",[10,3200,3201],{},[112,3202],{"alt":3203,"dataZoomable":76,"height":1393,"loading":116,"src":3204,"width":3205},"Dynamic OG image with Blog Post title","\u002Fcontent\u002Fpost_og.png",2804,[57,3207,3209],{"id":3208},"update-astro-20-content-collections","Update: Astro 2.0 Content Collections",[10,3211,3212,3217],{},[17,3213,3216],{"href":3214,"rel":3215},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Fguides\u002Fcontent-collections\u002F",[21],"Content Collections API","\nin Astro 2.0 simplifies a lot of code required to query and access the\nMarkdown\u002FMDX file contents in a Type-safe way.",[10,3219,3220,3221,3224],{},"Assuming we have a content collections for ",[23,3222,3223],{},"posts"," set up like so:",[10,3226,3227],{},[112,3228],{"alt":3229,"dataZoomable":76,"height":3230,"loading":116,"src":3231,"width":3232},"Content Collections in Astro 2.0",634,"\u002Fcontent\u002Fcollection_directory.png",444,[10,3234,3235,3236,3239],{},"With Collection ",[23,3237,3238],{},"src\u002Fcontent\u002Fconfig.ts"," file as:",[71,3241,3243],{"className":137,"code":3242,"language":139,"meta":76,"style":76},"import { defineCollection, z } from \"astro:content\";\n\nconst postsCollection = defineCollection({\n  schema: z.object({\n    title: z.string(),\n    description: z.string(),\n    \u002F\u002F ...\n  }),\n});\n\nexport const collections = {\n  post: postsCollection,\n};\n",[23,3244,3245,3259,3263,3277,3287,3297,3306,3311,3315,3319,3323,3338,3343],{"__ignoreMap":76},[80,3246,3247,3249,3252,3254,3257],{"class":82,"line":83},[80,3248,147],{"class":146},[80,3250,3251],{"class":153}," { defineCollection, z } ",[80,3253,157],{"class":146},[80,3255,3256],{"class":90}," \"astro:content\"",[80,3258,163],{"class":153},[80,3260,3261],{"class":82,"line":166},[80,3262,170],{"emptyLinePlaceholder":169},[80,3264,3265,3267,3270,3272,3275],{"class":82,"line":173},[80,3266,2033],{"class":146},[80,3268,3269],{"class":280}," postsCollection",[80,3271,284],{"class":146},[80,3273,3274],{"class":86}," defineCollection",[80,3276,1474],{"class":153},[80,3278,3279,3282,3285],{"class":82,"line":250},[80,3280,3281],{"class":153},"  schema: z.",[80,3283,3284],{"class":86},"object",[80,3286,1474],{"class":153},[80,3288,3289,3292,3294],{"class":82,"line":274},[80,3290,3291],{"class":153},"    title: z.",[80,3293,2294],{"class":86},[80,3295,3296],{"class":153},"(),\n",[80,3298,3299,3302,3304],{"class":82,"line":296},[80,3300,3301],{"class":153},"    description: z.",[80,3303,2294],{"class":86},[80,3305,3296],{"class":153},[80,3307,3308],{"class":82,"line":302},[80,3309,3310],{"class":927},"    \u002F\u002F ...\n",[80,3312,3313],{"class":82,"line":308},[80,3314,1510],{"class":153},[80,3316,3317],{"class":82,"line":314},[80,3318,1515],{"class":153},[80,3320,3321],{"class":82,"line":320},[80,3322,170],{"emptyLinePlaceholder":169},[80,3324,3325,3327,3330,3333,3335],{"class":82,"line":326},[80,3326,176],{"class":146},[80,3328,3329],{"class":146}," const",[80,3331,3332],{"class":280}," collections",[80,3334,284],{"class":146},[80,3336,3337],{"class":153}," {\n",[80,3339,3340],{"class":82,"line":332},[80,3341,3342],{"class":153},"  post: postsCollection,\n",[80,3344,3345],{"class":82,"line":338},[80,3346,386],{"class":153},[10,3348,3349,3350,3357],{},"Now using Content Collection Query function\n",[17,3351,3354],{"href":3352,"rel":3353},"https:\u002F\u002Fdocs.astro.build\u002Fen\u002Freference\u002Fapi-reference\u002F#getentrybyslug",[21],[23,3355,3356],{},"getEntryBySlug","\nwe can query the post by slug in just one line and render our OG image with the\nrequired data from the post.",[10,3359,3360,198],{},[23,3361,3362],{},"pages\u002Fpost\u002F[slug].og.ts",[71,3364,3366],{"className":137,"code":3365,"language":139,"meta":76,"style":76},"import type { APIContext } from \"astro\";\nimport { getEntryBySlug } from \"astro:content\";\nimport { readFileSync } from \"node:fs\";\nimport { html } from \"satori-html\";\nimport satori from \"satori\";\nimport sharp from \"sharp\";\n\nexport async function GET({ params }: APIContext) {\n  const { slug } = params;\n  \u002F\u002F\u002F One line to get the post from our collection using slug\n  const post = await getEntryBySlug(\"post\", slug!!);\n  const title = post?.data.title ?? \"My Post\";\n\n  const fontFilePath = `${process.cwd()}\u002Fpublic\u002Ffonts\u002FOptimistic_Display_Bold.ttf`;\n  const fontFile = readFileSync(fontFilePath);\n\n  const markup = html(`\u003Cdiv\n    style=\"height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgb(45,26,84); font-size: 32px; font-weight: 600;\"\n  >\n    \u003Cdiv\n      style=\"font-size: 70px; margin-top: 38px; display: flex; flex-direction: column; color: white;\"\n    >\n      ${title}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>`);\n  const svg = await satori(markup, {\n    width: 1200,\n    height: 630,\n    fonts: [\n      {\n        name: \"Optimistic Display\",\n        data: fontFile,\n        style: \"normal\",\n      },\n    ],\n  });\n  const png = sharp(Buffer.from(svg)).png();\n  const response = await png.toBuffer();\n\n  return new Response(response, {\n    status: 200,\n    headers: {\n      \"Content-Type\": \"image\u002Fpng\",\n      \"Cache-Control\": \"s-maxage=1, stale-while-revalidate=59\",\n    },\n  });\n};\n",[23,3367,3368,3382,3395,3408,3420,3432,3444,3448,3470,3484,3489,3515,3535,3539,3561,3573,3577,3592,3596,3600,3604,3608,3612,3620,3624,3630,3644,3652,3660,3664,3668,3676,3680,3688,3692,3696,3700,3720,3736,3740,3750,3758,3762,3772,3784,3788,3792],{"__ignoreMap":76},[80,3369,3370,3372,3374,3376,3378,3380],{"class":82,"line":83},[80,3371,147],{"class":146},[80,3373,150],{"class":146},[80,3375,154],{"class":153},[80,3377,157],{"class":146},[80,3379,160],{"class":90},[80,3381,163],{"class":153},[80,3383,3384,3386,3389,3391,3393],{"class":82,"line":166},[80,3385,147],{"class":146},[80,3387,3388],{"class":153}," { getEntryBySlug } ",[80,3390,157],{"class":146},[80,3392,3256],{"class":90},[80,3394,163],{"class":153},[80,3396,3397,3399,3401,3403,3406],{"class":82,"line":173},[80,3398,147],{"class":146},[80,3400,437],{"class":153},[80,3402,157],{"class":146},[80,3404,3405],{"class":90}," \"node:fs\"",[80,3407,163],{"class":153},[80,3409,3410,3412,3414,3416,3418],{"class":82,"line":250},[80,3411,147],{"class":146},[80,3413,236],{"class":153},[80,3415,157],{"class":146},[80,3417,241],{"class":90},[80,3419,163],{"class":153},[80,3421,3422,3424,3426,3428,3430],{"class":82,"line":274},[80,3423,147],{"class":146},[80,3425,771],{"class":153},[80,3427,157],{"class":146},[80,3429,776],{"class":90},[80,3431,163],{"class":153},[80,3433,3434,3436,3438,3440,3442],{"class":82,"line":296},[80,3435,147],{"class":146},[80,3437,1156],{"class":153},[80,3439,157],{"class":146},[80,3441,1161],{"class":90},[80,3443,163],{"class":153},[80,3445,3446],{"class":82,"line":302},[80,3447,170],{"emptyLinePlaceholder":169},[80,3449,3450,3452,3454,3456,3458,3460,3462,3464,3466,3468],{"class":82,"line":308},[80,3451,176],{"class":146},[80,3453,179],{"class":146},[80,3455,182],{"class":146},[80,3457,185],{"class":86},[80,3459,188],{"class":153},[80,3461,192],{"class":191},[80,3463,195],{"class":153},[80,3465,198],{"class":146},[80,3467,201],{"class":86},[80,3469,271],{"class":153},[80,3471,3472,3474,3476,3478,3480,3482],{"class":82,"line":314},[80,3473,277],{"class":146},[80,3475,2036],{"class":153},[80,3477,2106],{"class":280},[80,3479,2042],{"class":153},[80,3481,1612],{"class":146},[80,3483,2160],{"class":153},[80,3485,3486],{"class":82,"line":320},[80,3487,3488],{"class":927},"  \u002F\u002F\u002F One line to get the post from our collection using slug\n",[80,3490,3491,3493,3495,3497,3499,3502,3504,3507,3510,3513],{"class":82,"line":326},[80,3492,277],{"class":146},[80,3494,2431],{"class":280},[80,3496,284],{"class":146},[80,3498,615],{"class":146},[80,3500,3501],{"class":86}," getEntryBySlug",[80,3503,290],{"class":153},[80,3505,3506],{"class":90},"\"post\"",[80,3508,3509],{"class":153},", slug",[80,3511,3512],{"class":146},"!!",[80,3514,380],{"class":153},[80,3516,3517,3519,3522,3524,3527,3530,3533],{"class":82,"line":332},[80,3518,277],{"class":146},[80,3520,3521],{"class":280}," title",[80,3523,284],{"class":146},[80,3525,3526],{"class":153}," post?.data.title ",[80,3528,3529],{"class":146},"??",[80,3531,3532],{"class":90}," \"My Post\"",[80,3534,163],{"class":153},[80,3536,3537],{"class":82,"line":338},[80,3538,170],{"emptyLinePlaceholder":169},[80,3540,3541,3543,3545,3547,3549,3551,3553,3555,3557,3559],{"class":82,"line":344},[80,3542,277],{"class":146},[80,3544,555],{"class":280},[80,3546,284],{"class":146},[80,3548,560],{"class":90},[80,3550,563],{"class":153},[80,3552,210],{"class":90},[80,3554,568],{"class":86},[80,3556,571],{"class":90},[80,3558,574],{"class":90},[80,3560,163],{"class":153},[80,3562,3563,3565,3567,3569,3571],{"class":82,"line":350},[80,3564,277],{"class":146},[80,3566,589],{"class":280},[80,3568,284],{"class":146},[80,3570,594],{"class":86},[80,3572,597],{"class":153},[80,3574,3575],{"class":82,"line":356},[80,3576,170],{"emptyLinePlaceholder":169},[80,3578,3579,3581,3583,3585,3587,3589],{"class":82,"line":362},[80,3580,277],{"class":146},[80,3582,281],{"class":280},[80,3584,284],{"class":146},[80,3586,287],{"class":86},[80,3588,290],{"class":153},[80,3590,3591],{"class":90},"`\u003Cdiv\n",[80,3593,3594],{"class":82,"line":368},[80,3595,305],{"class":90},[80,3597,3598],{"class":82,"line":374},[80,3599,311],{"class":90},[80,3601,3602],{"class":82,"line":383},[80,3603,317],{"class":90},[80,3605,3606],{"class":82,"line":545},[80,3607,323],{"class":90},[80,3609,3610],{"class":82,"line":550},[80,3611,329],{"class":90},[80,3613,3614,3616,3618],{"class":82,"line":579},[80,3615,2996],{"class":90},[80,3617,1837],{"class":153},[80,3619,3002],{"class":90},[80,3621,3622],{"class":82,"line":584},[80,3623,371],{"class":90},[80,3625,3626,3628],{"class":82,"line":600},[80,3627,377],{"class":90},[80,3629,380],{"class":153},[80,3631,3632,3634,3636,3638,3640,3642],{"class":82,"line":605},[80,3633,277],{"class":146},[80,3635,610],{"class":280},[80,3637,284],{"class":146},[80,3639,615],{"class":146},[80,3641,94],{"class":86},[80,3643,620],{"class":153},[80,3645,3646,3648,3650],{"class":82,"line":623},[80,3647,626],{"class":153},[80,3649,629],{"class":280},[80,3651,632],{"class":153},[80,3653,3654,3656,3658],{"class":82,"line":635},[80,3655,638],{"class":153},[80,3657,641],{"class":280},[80,3659,632],{"class":153},[80,3661,3662],{"class":82,"line":646},[80,3663,649],{"class":153},[80,3665,3666],{"class":82,"line":652},[80,3667,655],{"class":153},[80,3669,3670,3672,3674],{"class":82,"line":658},[80,3671,661],{"class":153},[80,3673,664],{"class":90},[80,3675,632],{"class":153},[80,3677,3678],{"class":82,"line":669},[80,3679,672],{"class":153},[80,3681,3682,3684,3686],{"class":82,"line":675},[80,3683,678],{"class":153},[80,3685,681],{"class":90},[80,3687,632],{"class":153},[80,3689,3690],{"class":82,"line":686},[80,3691,689],{"class":153},[80,3693,3694],{"class":82,"line":692},[80,3695,695],{"class":153},[80,3697,3698],{"class":82,"line":698},[80,3699,701],{"class":153},[80,3701,3702,3704,3706,3708,3710,3712,3714,3716,3718],{"class":82,"line":704},[80,3703,277],{"class":146},[80,3705,1201],{"class":280},[80,3707,284],{"class":146},[80,3709,1206],{"class":86},[80,3711,1209],{"class":153},[80,3713,157],{"class":86},[80,3715,1214],{"class":153},[80,3717,1217],{"class":86},[80,3719,1220],{"class":153},[80,3721,3722,3724,3726,3728,3730,3732,3734],{"class":82,"line":1002},[80,3723,277],{"class":146},[80,3725,1227],{"class":280},[80,3727,284],{"class":146},[80,3729,615],{"class":146},[80,3731,1234],{"class":153},[80,3733,1237],{"class":86},[80,3735,1220],{"class":153},[80,3737,3738],{"class":82,"line":1007},[80,3739,170],{"emptyLinePlaceholder":169},[80,3741,3742,3744,3746,3748],{"class":82,"line":1012},[80,3743,1015],{"class":146},[80,3745,1018],{"class":146},[80,3747,1021],{"class":86},[80,3749,1346],{"class":153},[80,3751,3752,3754,3756],{"class":82,"line":1027},[80,3753,1030],{"class":153},[80,3755,1033],{"class":280},[80,3757,632],{"class":153},[80,3759,3760],{"class":82,"line":1038},[80,3761,1041],{"class":153},[80,3763,3764,3766,3768,3770],{"class":82,"line":1044},[80,3765,1047],{"class":90},[80,3767,1050],{"class":153},[80,3769,1367],{"class":90},[80,3771,632],{"class":153},[80,3773,3774,3777,3779,3782],{"class":82,"line":1058},[80,3775,3776],{"class":90},"      \"Cache-Control\"",[80,3778,1050],{"class":153},[80,3780,3781],{"class":90},"\"s-maxage=1, stale-while-revalidate=59\"",[80,3783,632],{"class":153},[80,3785,3786],{"class":82,"line":1064},[80,3787,1061],{"class":153},[80,3789,3790],{"class":82,"line":1069},[80,3791,701],{"class":153},[80,3793,3794],{"class":82,"line":3049},[80,3795,386],{"class":153},[10,3797,3798,3799,3802],{},"There you go! On visiting ",[23,3800,3801],{},"\u002Fpost\u002Fhello.og"," we should see the same OG Image as\nbefore.",[10,3804,3805,3806,3809,3810,3812],{},"In our GET route we can also ",[23,3807,3808],{},"fetch"," external API based on the current ",[23,3811,1954],{},"\nparams and we can use that data in our html markup template.",[10,3814,3815,3816],{},"I've made this into a simple github repo if you want to check out the full\nsource: ",[17,3817,3818],{"href":3818,"rel":3819},"https:\u002F\u002Fgithub.com\u002Frumaan\u002Fastro-vercel-og",[21],[10,3821,3822],{},"Further Reading \u002F References:",[1924,3824,3825,3832,3839],{},[1927,3826,3827],{},[17,3828,3831],{"href":3829,"rel":3830},"https:\u002F\u002Fogp.me\u002F",[21],"The Open Graph Protocol",[1927,3833,3834],{},[17,3835,3838],{"href":3836,"rel":3837},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FRequest",[21],"Request Web API",[1927,3840,3841],{},[17,3842,3844],{"href":19,"rel":3843},[21],"Vercel OG Image",[3846,3847,3848],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .sIZOC, html code.shiki .sIZOC{--shiki-default:#79B8FF;--shiki-default-font-weight:bold}",{"title":76,"searchDepth":166,"depth":166,"links":3850},[3851,3852,3853,3854,3855,3856,3857],{"id":59,"depth":166,"text":60},{"id":121,"depth":166,"text":122},{"id":1106,"depth":166,"text":1107},{"id":1404,"depth":166,"text":1405},{"id":1578,"depth":166,"text":1579},{"id":1943,"depth":166,"text":1944},{"id":3208,"depth":166,"text":3209},"2022-11-15","Generate an Open Graph image per page with Satori and an Astro server endpoint. Write the card as JSX, load the font, render it to SVG, then convert the SVG to a PNG.",false,"2026-08-29T17:42:32+07:00",{},"\u002Fblog\u002Fopen-graph-images-using-satori","11 min read",{"title":5,"description":3859},"blog\u002Fopen-graph-images-using-satori","3DLnEXvrQi6co7NFbCwG7eS758Mo5QkiaJOVD-flmTU",1788101292685]