-
Notifications
You must be signed in to change notification settings - Fork 12k
perf: og image caching v2 #23189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: og image caching v2 #23189
Changes from all commits
5bc55e4
d7f1b3c
4818984
5723494
900901f
882fe57
fc497ed
f944934
b603523
3ffcc06
7c295b6
269978d
c6ef44e
c1827ec
3358d0c
525991d
41ad619
3262520
c79cc09
54264aa
cce6cb3
d83024a
5984bd1
d6361e1
ea3b95f
050e89c
f65efff
3084d3f
82db1b9
85b96dc
b153568
eaeaafb
7d89703
318841b
0d495e1
f5a4536
74feba7
d19d7f1
58ef2c8
d104f32
d1c7ef2
088ae6b
6853b59
bdf6728
cb19243
3e58816
d03f4d1
576ec64
9cfb7d1
9fa732c
6d8ad38
1485245
5093f64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,21 +17,21 @@ export const generateMetadata = async ({ params }: _PageProps) => { | |
| if (!p.success) { | ||
| return notFound(); | ||
| } | ||
|
|
||
| const props = await getStaticProps(p.data.slug); | ||
| const slugFromUrl = p.data.slug; | ||
| const props = await getStaticProps(slugFromUrl); | ||
|
|
||
| if (!props) { | ||
| notFound(); | ||
| } | ||
| const { name, logo, description } = props.data; | ||
| const { name, logo, dirName: appStoreDirSlug, slug: appSlug, description } = props.data; | ||
|
|
||
| return await generateAppMetadata( | ||
| { slug: logo, name, description }, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously, |
||
| { slug: appStoreDirSlug ?? appSlug, logoUrl: logo, name, description }, | ||
| () => name, | ||
| () => description, | ||
| undefined, | ||
| undefined, | ||
| `/apps/${p.data.slug}` | ||
| `/apps/${appSlug}` | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ import type { NextRequest } from "next/server"; | |
| import type { SatoriOptions } from "satori"; | ||
| import { z, ZodError } from "zod"; | ||
|
|
||
| import { Meeting, App, Generic } from "@calcom/lib/OgImages"; | ||
| import { Meeting, App, Generic, getOGImageVersion } from "@calcom/lib/OgImages"; | ||
| import { WEBAPP_URL } from "@calcom/lib/constants"; | ||
|
|
||
| export const runtime = "edge"; | ||
hbjORbj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
@@ -22,6 +22,7 @@ const appSchema = z.object({ | |
| name: z.string(), | ||
| description: z.string(), | ||
| slug: z.string(), | ||
| logoUrl: z.string(), | ||
hbjORbj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| const genericSchema = z.object({ | ||
|
|
@@ -74,6 +75,7 @@ async function handler(req: NextRequest) { | |
| imageType, | ||
| }); | ||
|
|
||
| const etag = await getOGImageVersion("meeting"); | ||
| const img = new ImageResponse( | ||
| ( | ||
| <Meeting | ||
|
|
@@ -89,7 +91,9 @@ async function handler(req: NextRequest) { | |
| status: 200, | ||
| headers: { | ||
| "Content-Type": "image/png", | ||
| "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", | ||
| "Cache-Control": | ||
| "public, max-age=31536000, immutable, s-maxage=31536000, stale-while-revalidate=31536000", | ||
| ETag: `"${etag}"`, | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
|
|
@@ -111,19 +115,32 @@ async function handler(req: NextRequest) { | |
| } | ||
| case "app": { | ||
| try { | ||
| const { name, description, slug } = appSchema.parse({ | ||
| const { name, description, slug, logoUrl } = appSchema.parse({ | ||
| name: searchParams.get("name"), | ||
| description: searchParams.get("description"), | ||
| slug: searchParams.get("slug"), | ||
| logoUrl: searchParams.get("logoUrl"), | ||
| imageType, | ||
| }); | ||
| const img = new ImageResponse(<App name={name} description={description} slug={slug} />, ogConfig); | ||
|
|
||
| // Get SVG hash for the app | ||
| const svgHashesModule = await import("@calcom/web/public/app-store/svg-hashes.json"); | ||
| const SVG_HASHES = svgHashesModule.default ?? {}; | ||
| const svgHash = SVG_HASHES[slug] ?? undefined; | ||
|
Comment on lines
+127
to
+129
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
{
"zoomvideo": "d1c78abf",
"zohocrm": "102d69df",
"zohocalendar": "a9a369fe",
"zoho-bigin": "d3ae97fd",
"zapier": "9a1570dd",
"wordpress": "d572db7e",
"wipemycalother": "e4bb0e21",
"whereby": "65fd921f",
"whatsapp": "4025a2c2",
"weather_in_your_calendar": "4111fee5",
"vital": "86b4bf1e",
"vimcal": "8001675b",
"umami": "0593be46",
"twipla": "86191ba1",
"templates/link-as-an-app": "506ff3da",
"templates/general-app-settings": "506ff3da",
"templates/event-type-location-video-static": "506ff3da",
"templates/event-type-app-card": "506ff3da",
"templates/booking-pages-tag": "506ff3da",
"templates/basic": "506ff3da",
"telli": "202f4d2a",
"telegram": "73f9a0ed",
"tandemvideo": "7eb7508a",
"synthflow": "283524ad",
"sylapsvideo": "3e48b068",
"stripepayment": "11b0b1ee",
"skype": "4176a41c",
"sirius_video": "7c49e446",
"signal": "0736faad",
"salesroom": "b5a77692",
"routing-forms": "d6cfc1dd",
"roam": "4c6094e4",
"riverside": "61e15a9d",
"retell-ai": "f90d2781",
"raycast": "d94a2a01",
"qr_code": "9efd6cef",
"posthog": "8681a845",
"plausible": "91049a3a",
"pipedrive-crm": "673e0931",
"pipedream": "5600de90",
"ping": "5bb01833",
"paypal": "a540c421",
"office365video": "97d4df35",
"office365calendar": "ac5dd392",
"nextcloudtalk": "d0266d7a",
"n8n": "c92f3b72",
"monobot": "6d601f42",
"mock-payment-app": "506ff3da",
"mirotalk": "f5298670",
"metapixel": "fa32781d",
"matomo": "7c2e329e",
"make": "ca519871",
"linear": "f78f9869",
"lindy": "c7ab2989",
"larkcalendar": "f93540ea",
"jitsivideo": "79fdec8b",
"jelly": "040ad0e1",
"intercom": "fd949dc0",
"insihts": "aa7e93d7",
"ics-feedcalendar": "44c4adaa",
"huddle01video": "81a0653b",
"hubspot": "a6e01fff",
"horizon-workrooms": "fa32781d",
"hitpay": "9f0a5120",
"gtm": "65847f31",
"greetmate-ai": "025e82b7",
"granola": "2829eb38",
"googlevideo": "e4bb0e21",
"googlecalendar": "062af390",
"giphy": "c67e5a9b",
"ga4": "9822cbf6",
"feishucalendar": "f93540ea",
"fathom": "9fde6e4c",
"facetime": "09f45b11",
"exchangecalendar": "a63ab3e4",
"exchange2016calendar": "a63ab3e4",
"exchange2013calendar": "a63ab3e4",
"elevenlabs": "9ae79100",
"element-call": "8c79e11f",
"eightxeight": "3a97ea08",
"dub": "db4e9834",
"discord": "147f41c9",
"dialpad": "aef13faa",
"demodesk": "fd72da43",
"deel": "5e1041f9",
"dailyvideo": "9567de52",
"closecom": "45302531",
"chatbase": "332dee04",
"campfire": "584fd592",
"caldavcalendar": "70757035",
"btcpayserver": "02235e9e",
"bolna": "6fd7aa5f",
"basecamp3": "de61c3ec",
"baa-for-hipaa": "735c46eb",
"autocheckin": "94ffa8f2",
"attio": "0bc3964f",
"applecalendar": "1fde27dd",
"amie": "f9c089a1",
"alby": "40854e92"
}The key for Zoom, for example, is |
||
|
|
||
| const etag = await getOGImageVersion("app", { svgHash }); | ||
| const img = new ImageResponse( | ||
| <App name={name} description={description} slug={slug} logoUrl={logoUrl} />, | ||
| ogConfig | ||
| ); | ||
|
|
||
| return new Response(img.body, { | ||
| status: 200, | ||
| headers: { | ||
| "Content-Type": "image/png", | ||
| "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", | ||
| "Cache-Control": | ||
| "public, max-age=31536000, immutable, s-maxage=31536000, stale-while-revalidate=31536000", | ||
| ETag: `"${etag}"`, | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
|
|
@@ -151,13 +168,16 @@ async function handler(req: NextRequest) { | |
| imageType, | ||
| }); | ||
|
|
||
| const etag = await getOGImageVersion("generic"); | ||
| const img = new ImageResponse(<Generic title={title} description={description} />, ogConfig); | ||
|
|
||
| return new Response(img.body, { | ||
| status: 200, | ||
| headers: { | ||
| "Content-Type": "image/png", | ||
| "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", | ||
| "Cache-Control": | ||
| "public, max-age=31536000, immutable, s-maxage=31536000, stale-while-revalidate=31536000", | ||
| ETag: `"${etag}"`, | ||
| }, | ||
| }); | ||
| } catch (error) { | ||
|
|
@@ -178,9 +198,9 @@ async function handler(req: NextRequest) { | |
| } | ||
|
|
||
| default: | ||
| return new Response("What you're looking for is not here..", { status: 404 }); | ||
| return new Response("Wrong image type", { status: 404 }); | ||
| } | ||
| } catch (error) { | ||
| } catch { | ||
| return new Response("Internal server error", { status: 500 }); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| declare module "@calcom/web/public/app-store/svg-hashes.json" { | ||
| const value: Record<string, string>; | ||
| export default value; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,45 @@ | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const glob = require("glob"); | ||
| const crypto = require("crypto"); | ||
|
|
||
| const copyAppStoreStatic = () => { | ||
| // Get all static files from app-store packages | ||
| const staticFiles = glob.sync("../../packages/app-store/**/static/**/*", { nodir: true }); | ||
|
|
||
hbjORbj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Object to store icon SVG hashes | ||
| const SVG_HASHES = {}; | ||
|
|
||
| staticFiles.forEach((file) => { | ||
| // Extract app name from path | ||
| const appNameMatch = file.match(/app-store\/(.*?)\/static/); | ||
| if (!appNameMatch) return; | ||
|
|
||
| const appName = appNameMatch[1]; | ||
| const appDirName = appNameMatch[1]; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. more precise name. For Zoom, we could think |
||
| const fileName = path.basename(file); | ||
|
|
||
| // Create destination directory if it doesn't exist | ||
| const destDir = path.join(process.cwd(), "public", "app-store", appName); | ||
| const destDir = path.join(process.cwd(), "public", "app-store", appDirName); | ||
| if (!fs.existsSync(destDir)) { | ||
| fs.mkdirSync(destDir, { recursive: true }); | ||
| } | ||
|
|
||
| // Copy file to destination (Turborepo caching handles change detection) | ||
| const destPath = path.join(destDir, fileName); | ||
| fs.copyFileSync(file, destPath); | ||
|
|
||
| // If it's an icon SVG file, compute hash | ||
| if (fileName.includes("icon") && fileName.endsWith(".svg")) { | ||
| const content = fs.readFileSync(file, "utf8"); | ||
| const hash = crypto.createHash("md5").update(content).digest("hex").slice(0, 8); | ||
| SVG_HASHES[appDirName] = hash; | ||
| } | ||
|
|
||
| console.log(`Copied ${file} to ${destPath}`); | ||
| }); | ||
|
|
||
| // Write SVG hashes to a JSON file | ||
| const hashFilePath = path.join(process.cwd(), "public", "app-store", "svg-hashes.json"); | ||
| fs.writeFileSync(hashFilePath, JSON.stringify(SVG_HASHES, null, 2)); | ||
| }; | ||
|
|
||
| // Run the copy function | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props.data.dirName:zoomvideo(as in "packages/app-store/zoomvideo/....")props.data.slug:zoom