diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 810e530..7e8ad11 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -235,6 +235,9 @@ importers:
specifier: ^6.0.3
version: 6.0.3
devDependencies:
+ '@resvg/resvg-js':
+ specifier: ^2.6.2
+ version: 2.6.2
'@types/react':
specifier: ^19.2.16
version: 19.2.16
@@ -1354,6 +1357,86 @@ packages:
react: '>= 16.8'
react-dom: '>= 16.8'
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ resolution: {integrity: sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [android]
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ resolution: {integrity: sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ resolution: {integrity: sha512-nmok2LnAd6nLUKI16aEB9ydMC6Lidiiq2m1nEBDR1LaaP7FGs4AJ90qDraxX+CWlVuRlvNjyYJTNv8qFjtL9+A==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ resolution: {integrity: sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ resolution: {integrity: sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ resolution: {integrity: sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ resolution: {integrity: sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ resolution: {integrity: sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ resolution: {integrity: sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ resolution: {integrity: sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ resolution: {integrity: sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ resolution: {integrity: sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@resvg/resvg-js@2.6.2':
+ resolution: {integrity: sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==}
+ engines: {node: '>= 10'}
+
'@rolldown/binding-android-arm64@1.0.3':
resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5492,6 +5575,57 @@ snapshots:
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
+ '@resvg/resvg-js-android-arm-eabi@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-android-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-arm64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-darwin-x64@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm-gnueabihf@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-arm64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-gnu@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-linux-x64-musl@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-arm64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-ia32-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js-win32-x64-msvc@2.6.2':
+ optional: true
+
+ '@resvg/resvg-js@2.6.2':
+ optionalDependencies:
+ '@resvg/resvg-js-android-arm-eabi': 2.6.2
+ '@resvg/resvg-js-android-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-arm64': 2.6.2
+ '@resvg/resvg-js-darwin-x64': 2.6.2
+ '@resvg/resvg-js-linux-arm-gnueabihf': 2.6.2
+ '@resvg/resvg-js-linux-arm64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-arm64-musl': 2.6.2
+ '@resvg/resvg-js-linux-x64-gnu': 2.6.2
+ '@resvg/resvg-js-linux-x64-musl': 2.6.2
+ '@resvg/resvg-js-win32-arm64-msvc': 2.6.2
+ '@resvg/resvg-js-win32-ia32-msvc': 2.6.2
+ '@resvg/resvg-js-win32-x64-msvc': 2.6.2
+
'@rolldown/binding-android-arm64@1.0.3':
optional: true
diff --git a/sites/web/package.json b/sites/web/package.json
index fcc771f..ae4dd81 100644
--- a/sites/web/package.json
+++ b/sites/web/package.json
@@ -6,7 +6,8 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
- "check:types": "astro check"
+ "check:types": "astro check",
+ "og": "node scripts/generate-og.mjs"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
@@ -27,6 +28,7 @@
"typescript": "^6.0.3"
},
"devDependencies": {
+ "@resvg/resvg-js": "^2.6.2",
"@types/react": "^19.2.16",
"@types/react-dom": "^19.2.3"
}
diff --git a/sites/web/public/og.png b/sites/web/public/og.png
new file mode 100644
index 0000000..08856fa
Binary files /dev/null and b/sites/web/public/og.png differ
diff --git a/sites/web/public/og.svg b/sites/web/public/og.svg
index 8731460..552452a 100644
--- a/sites/web/public/og.svg
+++ b/sites/web/public/og.svg
@@ -15,24 +15,24 @@
- react-call()
+ react-call()
-
+
Your component
- can await.
+ can await.
-
+
createCallable() · 1 KB · no deps · SSR · React Native
-
+
react-call.desko.dev
diff --git a/sites/web/scripts/generate-og.mjs b/sites/web/scripts/generate-og.mjs
new file mode 100644
index 0000000..aa1e50a
--- /dev/null
+++ b/sites/web/scripts/generate-og.mjs
@@ -0,0 +1,44 @@
+// Rasterizes public/og.svg into public/og.png.
+//
+// Why this exists: SVG is not a valid Open Graph image format. Reddit,
+// X/Twitter, Facebook, LinkedIn, Slack, Discord and iMessage all ignore an
+// `og:image` that points at an SVG and fall back to a generic placeholder, so
+// shared links show no preview. og.svg stays the editable source of truth and
+// this script bakes it into the PNG that the meta tags actually reference.
+//
+// Fonts are vendored as TTFs (scripts/og-fonts) and loaded explicitly so the
+// render is deterministic and on-brand (Geist + Fira Code) regardless of which
+// fonts the build machine happens to have installed — resvg renders no text at
+// all when it cannot resolve a font.
+//
+// Run after editing og.svg: pnpm --filter web og
+
+import { readFileSync, readdirSync, writeFileSync } from 'node:fs'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { Resvg } from '@resvg/resvg-js'
+
+const here = dirname(fileURLToPath(import.meta.url))
+const publicDir = join(here, '..', 'public')
+const fontsDir = join(here, 'og-fonts')
+
+const svg = readFileSync(join(publicDir, 'og.svg'), 'utf8')
+const fontFiles = readdirSync(fontsDir)
+ .filter((f) => f.endsWith('.ttf'))
+ .map((f) => join(fontsDir, f))
+
+const resvg = new Resvg(svg, {
+ // The SVG already declares width/height 1200x630; honor it 1:1.
+ fitTo: { mode: 'width', value: 1200 },
+ font: {
+ fontFiles,
+ loadSystemFonts: false,
+ defaultFontFamily: 'Geist',
+ },
+})
+
+const png = resvg.render().asPng()
+const out = join(publicDir, 'og.png')
+writeFileSync(out, png)
+
+console.log(`Wrote ${out} (${png.length} bytes)`)
diff --git a/sites/web/scripts/og-fonts/FiraCode-Regular.ttf b/sites/web/scripts/og-fonts/FiraCode-Regular.ttf
new file mode 100644
index 0000000..8ff1c40
Binary files /dev/null and b/sites/web/scripts/og-fonts/FiraCode-Regular.ttf differ
diff --git a/sites/web/scripts/og-fonts/Geist-Medium.ttf b/sites/web/scripts/og-fonts/Geist-Medium.ttf
new file mode 100644
index 0000000..8b295ed
Binary files /dev/null and b/sites/web/scripts/og-fonts/Geist-Medium.ttf differ
diff --git a/sites/web/scripts/og-fonts/Geist-Regular.ttf b/sites/web/scripts/og-fonts/Geist-Regular.ttf
new file mode 100644
index 0000000..67e6992
Binary files /dev/null and b/sites/web/scripts/og-fonts/Geist-Regular.ttf differ
diff --git a/sites/web/src/layouts/Base.astro b/sites/web/src/layouts/Base.astro
index 6e8f6ee..4010047 100644
--- a/sites/web/src/layouts/Base.astro
+++ b/sites/web/src/layouts/Base.astro
@@ -14,7 +14,10 @@ interface Props {
const {
title = 'react-call — your component can await',
description = 'createCallable turns any React component into something you can await. Dialogs, toasts, pickers, menus — any UI that conceptually returns a value to its caller.',
- image = '/og.svg',
+ // Must be a raster format: Reddit, X, Facebook, LinkedIn, Slack, Discord and
+ // iMessage all ignore an SVG og:image. og.png is generated from og.svg via
+ // `pnpm --filter web og` — see sites/web/scripts/generate-og.mjs.
+ image = '/og.png',
type = 'website',
noindex = false,
} = Astro.props
@@ -57,6 +60,10 @@ const imageUrl = new URL(image, site).href
+
+
+
+
@@ -64,6 +71,7 @@ const imageUrl = new URL(image, site).href
+