Skip to content

Commit 3fd3bc3

Browse files
chore: og image
1 parent 98efe72 commit 3fd3bc3

File tree

1 file changed

+178
-89
lines changed

1 file changed

+178
-89
lines changed

apps/sim/app/(landing)/blog/og/route.tsx

Lines changed: 178 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import fs from 'fs/promises'
2-
import path from 'path'
1+
import { readFile } from 'node:fs/promises'
2+
import { join } from 'node:path'
33
import { ImageResponse } from 'next/og'
44
import type { NextRequest } from 'next/server'
55
import { getPostMetaBySlug } from '@/lib/blog/registry'
@@ -8,13 +8,124 @@ import { getPrimaryCategory } from '@/app/(landing)/blog/tag-colors'
88

99
export const runtime = 'nodejs'
1010

11+
async function getLogoDataUrl(): Promise<string> {
12+
const logoPath = join(process.cwd(), 'public', 'logo', 'sim-landing.svg')
13+
const buffer = await readFile(logoPath)
14+
return `data:image/svg+xml;base64,${buffer.toString('base64')}`
15+
}
16+
1117
function getTitleFontSize(title: string): number {
1218
if (title.length > 80) return 36
1319
if (title.length > 60) return 40
1420
if (title.length > 40) return 48
1521
return 56
1622
}
1723

24+
async function loadGoogleFont(font: string, weights: string, text: string): Promise<ArrayBuffer> {
25+
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weights}&text=${encodeURIComponent(text)}`
26+
const css = await (await fetch(url)).text()
27+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
28+
29+
if (resource) {
30+
const response = await fetch(resource[1])
31+
if (response.status === 200) {
32+
return await response.arrayBuffer()
33+
}
34+
}
35+
36+
throw new Error('Failed to load font data')
37+
}
38+
39+
function Block({
40+
x,
41+
y,
42+
w,
43+
h,
44+
color,
45+
opacity = 1,
46+
}: {
47+
x: number
48+
y: number
49+
w: number
50+
h: number
51+
color: string
52+
opacity?: number
53+
}) {
54+
return (
55+
<div
56+
style={{
57+
position: 'absolute',
58+
left: x,
59+
top: y,
60+
width: w,
61+
height: h,
62+
borderRadius: 2.6,
63+
backgroundColor: color,
64+
opacity,
65+
}}
66+
/>
67+
)
68+
}
69+
70+
function BlocksLeft() {
71+
return (
72+
<div style={{ display: 'flex', position: 'relative', width: 34, height: 226 }}>
73+
<Block x={0} y={0} w={34} h={34} color='#FA4EDF' opacity={0.6} />
74+
<Block x={0} y={0} w={17} h={17} color='#FA4EDF' />
75+
<Block x={17} y={0} w={17} h={68} color='#FA4EDF' opacity={0.6} />
76+
<Block x={17} y={17} w={17} h={17} color='#FA4EDF' />
77+
<Block x={0} y={52} w={34} h={17} color='#FA4EDF' opacity={0.6} />
78+
<Block x={17} y={85} w={17} h={141} color='#00F701' opacity={0.6} />
79+
<Block x={0} y={120} w={17} h={17} color='#FFCC02' />
80+
<Block x={0} y={120} w={17} h={34} color='#FFCC02' opacity={0.4} />
81+
<Block x={0} y={154} w={17} h={17} color='#00F701' />
82+
<Block x={0} y={154} w={34} h={34} color='#00F701' opacity={0.5} />
83+
</div>
84+
)
85+
}
86+
87+
function BlocksRight() {
88+
return (
89+
<div style={{ display: 'flex', position: 'relative', width: 34, height: 205 }}>
90+
<Block x={0} y={0} w={17} h={17} color='#FA4EDF' opacity={0.6} />
91+
<Block x={17} y={0} w={17} h={17} color='#FA4EDF' opacity={0.6} />
92+
<Block x={17} y={0} w={34} h={17} color='#FA4EDF' opacity={0.6} />
93+
<Block x={17} y={17} w={17} h={68} color='#FA4EDF' opacity={0.6} />
94+
<Block x={17} y={34} w={17} h={17} color='#FA4EDF' />
95+
<Block x={0} y={34} w={34} h={17} color='#FA4EDF' opacity={0.6} />
96+
<Block x={0} y={69} w={34} h={17} color='#FA4EDF' opacity={0.6} />
97+
<Block x={17} y={102} w={17} h={102} color='#2ABBF8' opacity={0.6} />
98+
<Block x={0} y={137} w={17} h={17} color='#00F701' />
99+
<Block x={0} y={137} w={17} h={34} color='#00F701' opacity={0.4} />
100+
</div>
101+
)
102+
}
103+
104+
function BlocksTopRight() {
105+
return (
106+
<div style={{ display: 'flex', position: 'relative', width: 295, height: 34 }}>
107+
<Block x={0} y={0} w={17} h={34} color='#2ABBF8' />
108+
<Block x={0} y={0} w={17} h={17} color='#2ABBF8' />
109+
<Block x={0} y={0} w={85} h={17} color='#2ABBF8' opacity={0.6} />
110+
<Block x={34} y={0} w={34} h={34} color='#2ABBF8' opacity={0.6} />
111+
<Block x={34} y={0} w={17} h={17} color='#2ABBF8' />
112+
<Block x={52} y={17} w={17} h={17} color='#2ABBF8' />
113+
<Block x={68} y={0} w={55} h={17} color='#00F701' />
114+
<Block x={106} y={0} w={34} h={34} color='#00F701' opacity={0.6} />
115+
<Block x={106} y={0} w={51} h={17} color='#00F701' opacity={0.6} />
116+
<Block x={124} y={17} w={17} h={17} color='#00F701' />
117+
<Block x={157} y={0} w={34} h={17} color='#FFCC02' opacity={0.6} />
118+
<Block x={157} y={0} w={17} h={17} color='#FFCC02' />
119+
<Block x={209} y={0} w={17} h={34} color='#FA4EDF' opacity={0.6} />
120+
<Block x={209} y={0} w={68} h={17} color='#FA4EDF' opacity={0.6} />
121+
<Block x={243} y={0} w={34} h={34} color='#FA4EDF' opacity={0.6} />
122+
<Block x={243} y={0} w={17} h={17} color='#FA4EDF' />
123+
<Block x={260} y={0} w={34} h={17} color='#FA4EDF' opacity={0.6} />
124+
<Block x={261} y={17} w={17} h={17} color='#FA4EDF' />
125+
</div>
126+
)
127+
}
128+
18129
export async function GET(request: NextRequest) {
19130
const slug = request.nextUrl.searchParams.get('slug')
20131

@@ -32,36 +143,11 @@ export async function GET(request: NextRequest) {
32143
const authors = post.authors && post.authors.length > 0 ? post.authors : [post.author]
33144
const authorNames = authors.map((a) => a.name).join(', ')
34145

35-
let fontMedium: Buffer
36-
let fontBold: Buffer
37-
try {
38-
const fontsDirPrimary = path.join(process.cwd(), 'app', '_styles', 'fonts', 'season')
39-
const fontsDirFallback = path.join(
40-
process.cwd(),
41-
'apps',
42-
'sim',
43-
'app',
44-
'_styles',
45-
'fonts',
46-
'season'
47-
)
48-
49-
let fontsDir = fontsDirPrimary
50-
try {
51-
await fs.access(fontsDirPrimary)
52-
} catch {
53-
fontsDir = fontsDirFallback
54-
}
55-
56-
;[fontMedium, fontBold] = await Promise.all([
57-
fs.readFile(path.join(fontsDir, 'SeasonSans-Medium.woff')),
58-
fs.readFile(path.join(fontsDir, 'SeasonSans-Bold.woff')),
59-
])
60-
} catch {
61-
return new Response('Font assets not found', { status: 500 })
62-
}
63-
64-
const COLORS = ['#5fc5ff', '#F472B6', '#fcd34d', '#4BDE80', '#FF8533'] as const
146+
const allText = `${category.label}${post.readingTime ? `${post.readingTime} min read` : ''}${post.title}${post.description}${authorNames}${formatDate(new Date(post.date))}sim.ai/blog`
147+
const [fontData, logoDataUrl] = await Promise.all([
148+
loadGoogleFont('Inter', '400;500;700', allText),
149+
getLogoDataUrl(),
150+
])
65151

66152
return new ImageResponse(
67153
<div
@@ -73,7 +159,7 @@ export async function GET(request: NextRequest) {
73159
justifyContent: 'space-between',
74160
padding: '56px 64px',
75161
background: '#1C1C1C',
76-
fontFamily: 'Season Sans',
162+
fontFamily: 'Inter',
77163
position: 'relative',
78164
overflow: 'hidden',
79165
}}
@@ -100,68 +186,45 @@ export async function GET(request: NextRequest) {
100186
border: '1px solid #2A2A2A',
101187
}}
102188
/>
189+
103190
<div
104191
style={{
105-
display: 'flex',
106-
flexDirection: 'column',
107192
position: 'absolute',
108-
top: 0,
109-
left: 0,
193+
left: 96,
194+
top: 502,
195+
display: 'flex',
196+
transform: 'rotate(90deg)',
110197
}}
111198
>
112-
<div style={{ display: 'flex' }}>
113-
{COLORS.map((color) => (
114-
<div key={color} style={{ width: 16, height: 16, backgroundColor: color }} />
115-
))}
116-
</div>
117-
<div style={{ display: 'flex', flexDirection: 'column' }}>
118-
{COLORS.slice(0, 3).map((color) => (
119-
<div key={`v-${color}`} style={{ width: 16, height: 16, backgroundColor: color }} />
120-
))}
121-
</div>
199+
<BlocksLeft />
122200
</div>
201+
123202
<div
124203
style={{
204+
position: 'absolute',
205+
right: 0,
206+
top: 212,
125207
display: 'flex',
208+
}}
209+
>
210+
<BlocksRight />
211+
</div>
212+
213+
<div
214+
style={{
126215
position: 'absolute',
127-
bottom: 0,
128216
right: 0,
217+
top: 0,
218+
display: 'flex',
129219
}}
130220
>
131-
{[...COLORS].reverse().map((color) => (
132-
<div key={`b-${color}`} style={{ width: 16, height: 16, backgroundColor: color }} />
133-
))}
221+
<BlocksTopRight />
134222
</div>
135-
<div style={{ display: 'flex', alignItems: 'center', gap: 16, zIndex: 1 }}>
136-
<div
137-
style={{
138-
display: 'flex',
139-
alignItems: 'center',
140-
padding: '4px 12px',
141-
backgroundColor: category.color,
142-
color: '#000000',
143-
fontSize: 12,
144-
fontWeight: 700,
145-
textTransform: 'uppercase',
146-
letterSpacing: '0.1em',
147-
}}
148-
>
149-
{category.label}
150-
</div>
151-
{post.readingTime && (
152-
<span
153-
style={{
154-
fontSize: 13,
155-
color: '#666666',
156-
textTransform: 'uppercase',
157-
letterSpacing: '0.08em',
158-
fontWeight: 500,
159-
}}
160-
>
161-
{post.readingTime} min read
162-
</span>
163-
)}
223+
224+
<div style={{ display: 'flex', flexDirection: 'column', gap: 30, zIndex: 1 }}>
225+
<img src={logoDataUrl} alt='Sim' height={33} width={106.5} />
164226
</div>
227+
165228
<div
166229
style={{
167230
display: 'flex',
@@ -172,6 +235,36 @@ export async function GET(request: NextRequest) {
172235
justifyContent: 'center',
173236
}}
174237
>
238+
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
239+
<div
240+
style={{
241+
display: 'flex',
242+
alignItems: 'center',
243+
padding: '4px 12px',
244+
backgroundColor: category.color,
245+
color: '#000000',
246+
fontSize: 12,
247+
fontWeight: 700,
248+
textTransform: 'uppercase',
249+
letterSpacing: '0.1em',
250+
}}
251+
>
252+
{category.label}
253+
</div>
254+
{post.readingTime && (
255+
<span
256+
style={{
257+
fontSize: 13,
258+
color: '#666666',
259+
textTransform: 'uppercase',
260+
letterSpacing: '0.08em',
261+
fontWeight: 500,
262+
}}
263+
>
264+
{post.readingTime} min read
265+
</span>
266+
)}
267+
</div>
175268
<div
176269
style={{
177270
fontSize: getTitleFontSize(post.title),
@@ -199,14 +292,16 @@ export async function GET(request: NextRequest) {
199292
: post.description}
200293
</div>
201294
</div>
295+
202296
<div
203297
style={{
204298
display: 'flex',
205299
justifyContent: 'space-between',
206300
alignItems: 'center',
207301
zIndex: 1,
208302
borderTop: '1px solid #2A2A2A',
209-
paddingTop: 24,
303+
paddingTop: 30,
304+
marginBottom: 30,
210305
}}
211306
>
212307
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
@@ -240,17 +335,11 @@ export async function GET(request: NextRequest) {
240335
height: 630,
241336
fonts: [
242337
{
243-
name: 'Season Sans',
244-
data: fontMedium,
338+
name: 'Inter',
339+
data: fontData,
245340
style: 'normal' as const,
246341
weight: 500 as const,
247342
},
248-
{
249-
name: 'Season Sans',
250-
data: fontBold,
251-
style: 'normal' as const,
252-
weight: 700 as const,
253-
},
254343
],
255344
}
256345
)

0 commit comments

Comments
 (0)