Skip to content

Commit f7d1b06

Browse files
authored
feat(docs): add opengraph to docs for dynamic link preview (#2360)
1 parent 73940ab commit f7d1b06

File tree

4 files changed

+169
-2
lines changed

4 files changed

+169
-2
lines changed

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ export async function generateMetadata(props: {
243243
const baseUrl = 'https://docs.sim.ai'
244244
const fullUrl = `${baseUrl}${page.url}`
245245

246+
const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(page.data.title)}&category=DOCUMENTATION`
247+
246248
return {
247249
title: page.data.title,
248250
description:
@@ -272,12 +274,23 @@ export async function generateMetadata(props: {
272274
alternateLocale: ['en', 'es', 'fr', 'de', 'ja', 'zh']
273275
.filter((lang) => lang !== params.lang)
274276
.map((lang) => (lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`)),
277+
images: [
278+
{
279+
url: ogImageUrl,
280+
width: 1200,
281+
height: 630,
282+
alt: page.data.title,
283+
},
284+
],
275285
},
276286
twitter: {
277-
card: 'summary',
287+
card: 'summary_large_image',
278288
title: page.data.title,
279289
description:
280290
page.data.description || 'Sim visual workflow builder for AI applications documentation',
291+
images: [ogImageUrl],
292+
creator: '@simdotai',
293+
site: '@simdotai',
281294
},
282295
robots: {
283296
index: true,

apps/docs/app/api/og/route.tsx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { ImageResponse } from 'next/og'
2+
import type { NextRequest } from 'next/server'
3+
4+
export const runtime = 'edge'
5+
6+
const TITLE_FONT_SIZE = {
7+
large: 64,
8+
medium: 56,
9+
small: 48,
10+
} as const
11+
12+
function getTitleFontSize(title: string): number {
13+
if (title.length > 45) return TITLE_FONT_SIZE.small
14+
if (title.length > 30) return TITLE_FONT_SIZE.medium
15+
return TITLE_FONT_SIZE.large
16+
}
17+
18+
/**
19+
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
20+
*/
21+
async function loadGoogleFont(font: string, text: string): Promise<ArrayBuffer> {
22+
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@500;600&text=${encodeURIComponent(text)}`
23+
const css = await (await fetch(url)).text()
24+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
25+
26+
if (resource) {
27+
const response = await fetch(resource[1])
28+
if (response.status === 200) {
29+
return await response.arrayBuffer()
30+
}
31+
}
32+
33+
throw new Error('Failed to load font data')
34+
}
35+
36+
/**
37+
* Generates dynamic Open Graph images for documentation pages.
38+
*/
39+
export async function GET(request: NextRequest) {
40+
const { searchParams } = new URL(request.url)
41+
const title = searchParams.get('title') || 'Documentation'
42+
const category = searchParams.get('category') || 'DOCUMENTATION'
43+
44+
const baseUrl = new URL(request.url).origin
45+
const backgroundImageUrl = `${baseUrl}/static/og-background.png`
46+
47+
// Load Inter font dynamically from Google Fonts
48+
const allText = `${title}${category}docs.sim.ai`
49+
const fontData = await loadGoogleFont('Inter', allText)
50+
51+
return new ImageResponse(
52+
<div
53+
style={{
54+
height: '100%',
55+
width: '100%',
56+
display: 'flex',
57+
flexDirection: 'column',
58+
backgroundColor: '#121212',
59+
position: 'relative',
60+
fontFamily: 'Inter',
61+
}}
62+
>
63+
{/* Background texture */}
64+
<img
65+
src={backgroundImageUrl}
66+
alt=''
67+
style={{
68+
position: 'absolute',
69+
top: 0,
70+
left: 0,
71+
width: '100%',
72+
height: '100%',
73+
objectFit: 'cover',
74+
opacity: 0.06,
75+
}}
76+
/>
77+
78+
{/* Content */}
79+
<div
80+
style={{
81+
display: 'flex',
82+
flexDirection: 'column',
83+
padding: '60px 72px',
84+
height: '100%',
85+
justifyContent: 'space-between',
86+
}}
87+
>
88+
{/* Logo */}
89+
<img src={`${baseUrl}/static/logo.png`} alt='sim' height={36} />
90+
91+
{/* Category + Title */}
92+
<div
93+
style={{
94+
display: 'flex',
95+
flexDirection: 'column',
96+
gap: 16,
97+
}}
98+
>
99+
<span
100+
style={{
101+
fontSize: 14,
102+
fontWeight: 500,
103+
color: '#737373',
104+
letterSpacing: '0.08em',
105+
}}
106+
>
107+
{category}
108+
</span>
109+
<span
110+
style={{
111+
fontSize: getTitleFontSize(title),
112+
fontWeight: 600,
113+
color: '#ffffff',
114+
lineHeight: 1.15,
115+
letterSpacing: '-0.02em',
116+
}}
117+
>
118+
{title}
119+
</span>
120+
</div>
121+
122+
{/* Footer */}
123+
<span
124+
style={{
125+
fontSize: 16,
126+
fontWeight: 400,
127+
color: '#525252',
128+
}}
129+
>
130+
docs.sim.ai
131+
</span>
132+
</div>
133+
</div>,
134+
{
135+
width: 1200,
136+
height: 630,
137+
fonts: [
138+
{
139+
name: 'Inter',
140+
data: fontData,
141+
style: 'normal',
142+
},
143+
],
144+
}
145+
)
146+
}

apps/docs/app/layout.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ export const metadata = {
5656
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
5757
description:
5858
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
59+
images: [
60+
{
61+
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation&category=DOCUMENTATION',
62+
width: 1200,
63+
height: 630,
64+
alt: 'Sim Documentation',
65+
},
66+
],
5967
},
6068
twitter: {
6169
card: 'summary_large_image',
@@ -64,7 +72,7 @@ export const metadata = {
6472
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
6573
creator: '@simdotai',
6674
site: '@simdotai',
67-
images: ['/og-image.png'],
75+
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation&category=DOCUMENTATION'],
6876
},
6977
robots: {
7078
index: true,
583 KB
Loading

0 commit comments

Comments
 (0)