diff --git a/apps/monograph/app/routes/$id.tsx b/apps/monograph/app/routes/$id.tsx index 1dc99373be..dc26f8108a 100644 --- a/apps/monograph/app/routes/$id.tsx +++ b/apps/monograph/app/routes/$id.tsx @@ -46,7 +46,8 @@ type Monograph = { type MonographResponse = Omit & { content: string }; export const meta: MetaFunction = ({ data }) => { - if (!data) return []; + if (!data || !data.metadata || !data.monograph) return []; + const imageUrl = `${PUBLIC_URL}/api/og.png?${new URLSearchParams({ title: data?.metadata?.title || "", description: data?.metadata?.fullDescription diff --git a/apps/monograph/app/routes/api.og[.png].ts b/apps/monograph/app/routes/api.og[.png].ts index 804421c551..cb81d6a932 100644 --- a/apps/monograph/app/routes/api.og[.png].ts +++ b/apps/monograph/app/routes/api.og[.png].ts @@ -34,11 +34,14 @@ export async function loader({ request }: LoaderFunctionArgs) { }); return new Response( - await makeImage({ - date, - description, - title - }), + await makeImage( + { + date, + description, + title + }, + url.search + ), { status: 200, headers: { diff --git a/apps/monograph/app/utils/generate-og-image.server.tsx b/apps/monograph/app/utils/generate-og-image.server.tsx index e53f42ffee..624a78d845 100644 --- a/apps/monograph/app/utils/generate-og-image.server.tsx +++ b/apps/monograph/app/utils/generate-og-image.server.tsx @@ -22,123 +22,145 @@ import { ThemeDark } from "@notesnook/theme"; import sharp from "sharp"; import fontRegular from "../assets/fonts/open-sans-v34-vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic-regular.ttf?arraybuffer"; import fontBold from "../assets/fonts/open-sans-v34-vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic-600.ttf?arraybuffer"; +import {} from "lru-cache"; import { Readable } from "node:stream"; +import { LRUCache } from "lru-cache"; export type OGMetadata = { title: string; description: string; date: string }; -export async function makeImage(metadata: OGMetadata) { +const cache = new LRUCache({ + ttl: 1000 * 60 * 60 * 24, + ttlAutopurge: true +}); + +export async function makeImage(metadata: OGMetadata, cacheKey: string) { const theme = ThemeDark.scopes.base; - const svg = await satori( -
-
-

- {metadata.date} -

-

- {metadata.title} -

-

- {Buffer.from(metadata.description || "", "base64").toString("utf-8")} -

-
-
- - - - - - - - - - - - -
+ + console.time("satori"); + let svg = cache.get(cacheKey); + + if (!svg) { + svg = await satori( +
+

+ {metadata.date} +

+

- Notesnook Monograph -

+ {metadata.title} +

- Anonymous, secure, and encrypted note sharing with password - protection. + {Buffer.from(metadata.description || "", "base64").toString( + "utf-8" + )}

-
-
, - { - width: 1200, - height: 630, - fonts: [ - { - name: "Open Sans", - data: fontRegular!, - weight: 400, - style: "normal" - }, - { - name: "Open Sans", - data: fontBold!, - weight: 600, - style: "normal" - } - ] - } - ); +
+ + + + + + + + + + + + +
+

+ Notesnook Monograph +

+

+ Anonymous, secure, and encrypted note sharing with password + protection. +

+
+
+
, + { + width: 1200, + height: 630, + fonts: [ + { + name: "Open Sans", + data: fontRegular!, + weight: 400, + style: "normal" + }, + { + name: "Open Sans", + data: fontBold!, + weight: 600, + style: "normal" + } + ] + } + ); + cache.set(cacheKey, svg); + } + console.timeEnd("satori"); + return Readable.toWeb(sharp(Buffer.from(svg)).png()) as ReadableStream; } diff --git a/apps/monograph/app/utils/storage/index.ts b/apps/monograph/app/utils/storage/index.ts index 14bbc50124..15ed7579bc 100644 --- a/apps/monograph/app/utils/storage/index.ts +++ b/apps/monograph/app/utils/storage/index.ts @@ -32,7 +32,7 @@ export async function read(key: string, fallback: T) { } const value = (await provider).read(key, fallback); cache[key] = { - ttl: 5 * 60000, + ttl: 5 * 60 * 1000, value, cachedAt: Date.now() }; @@ -40,6 +40,10 @@ export async function read(key: string, fallback: T) { } export async function write(key: string, data: T) { + if (cache[key]) { + cache[key].value = data; + cache[key].cachedAt = Date.now(); + } return (await provider).write(key, data); } diff --git a/apps/monograph/package-lock.json b/apps/monograph/package-lock.json index 648c3e178c..aee899495e 100644 --- a/apps/monograph/package-lock.json +++ b/apps/monograph/package-lock.json @@ -30,6 +30,7 @@ "date-fns": "^4.1.0", "html-to-text": "^9.0.5", "isbot": "^5.1.17", + "lru-cache": "^11.0.2", "mac-scrollbar": "^0.13.6", "nanoid": "^5.0.7", "react": "18.3.1", @@ -434,6 +435,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6717,13 +6728,12 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "engines": { + "node": "20 || >=22" } }, "node_modules/mac-scrollbar": { diff --git a/apps/monograph/package.json b/apps/monograph/package.json index aa261961aa..22c770ffaf 100644 --- a/apps/monograph/package.json +++ b/apps/monograph/package.json @@ -33,6 +33,7 @@ "date-fns": "^4.1.0", "html-to-text": "^9.0.5", "isbot": "^5.1.17", + "lru-cache": "^11.0.2", "mac-scrollbar": "^0.13.6", "nanoid": "^5.0.7", "react": "18.3.1",