Skip to content

Commit 0233677

Browse files
carletexclaude
andauthored
Add dynamic OG images for blog posts (#28)
Edge API route generates social unfurl images with Share Tech Mono font, matching the site's dark/green/magenta terminal vibe. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6a906c3 commit 0233677

File tree

4 files changed

+307
-1
lines changed

4 files changed

+307
-1
lines changed

packages/nextjs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@heroicons/react": "^2.0.11",
2020
"@rainbow-me/rainbowkit": "^0.12.0",
2121
"@uniswap/sdk": "^3.0.3",
22+
"@vercel/og": "^0.9.0",
2223
"daisyui": "^2.31.0",
2324
"ethers": "^5.0.0",
2425
"gray-matter": "^4.0.3",

packages/nextjs/pages/api/og.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { NextRequest } from "next/server";
2+
import { ImageResponse } from "@vercel/og";
3+
4+
export const config = {
5+
runtime: "edge",
6+
};
7+
8+
export default async function handler(req: NextRequest) {
9+
const { searchParams } = new URL(req.url);
10+
const title = searchParams.get("title") || "BG Sand Garden";
11+
12+
// Load Share Tech Mono from Google Fonts (ttf format required by Satori)
13+
const font = await fetch("https://fonts.gstatic.com/s/sharetechmono/v16/J7aHnp1uDWRBEqV98dVQztYldFc7pA.ttf").then(
14+
res => res.arrayBuffer(),
15+
);
16+
17+
return new ImageResponse(
18+
(
19+
<div
20+
style={{
21+
display: "flex",
22+
flexDirection: "column",
23+
width: "100%",
24+
height: "100%",
25+
backgroundColor: "#000000",
26+
padding: "60px",
27+
justifyContent: "space-between",
28+
fontFamily: "Share Tech Mono",
29+
}}
30+
>
31+
{/* Top: Branding */}
32+
<div style={{ display: "flex", flexDirection: "column" }}>
33+
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
34+
<div
35+
style={{
36+
fontSize: 42,
37+
color: "#49ff13",
38+
fontWeight: 700,
39+
letterSpacing: "-1px",
40+
}}
41+
>
42+
BG Sand Garden
43+
</div>
44+
</div>
45+
<div
46+
style={{
47+
fontSize: 22,
48+
color: "#49ff13",
49+
opacity: 0.5,
50+
marginTop: "4px",
51+
}}
52+
>
53+
BuidlGuidl
54+
</div>
55+
</div>
56+
57+
{/* Center: Blog Title */}
58+
<div
59+
style={{
60+
display: "flex",
61+
fontSize: title.length > 60 ? 48 : title.length > 40 ? 56 : 64,
62+
color: "#ffffff",
63+
fontWeight: 700,
64+
lineHeight: 1.15,
65+
maxWidth: "95%",
66+
}}
67+
>
68+
{title}
69+
</div>
70+
71+
{/* Bottom: decorative line */}
72+
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
73+
<div
74+
style={{
75+
display: "flex",
76+
width: "60px",
77+
height: "3px",
78+
backgroundColor: "#c913ff",
79+
}}
80+
/>
81+
<div
82+
style={{
83+
display: "flex",
84+
width: "100%",
85+
height: "1px",
86+
backgroundColor: "#49ff13",
87+
opacity: 0.2,
88+
}}
89+
/>
90+
</div>
91+
</div>
92+
),
93+
{
94+
width: 1200,
95+
height: 630,
96+
fonts: [
97+
{
98+
name: "Share Tech Mono",
99+
data: font,
100+
style: "normal",
101+
weight: 400,
102+
},
103+
],
104+
},
105+
);
106+
}

packages/nextjs/pages/blog/[slug].tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ const BlogPost: NextPage<Props> = ({ source, meta }) => {
5555
<Head>
5656
<title>{meta.title} — Sand Garden</title>
5757
<meta name="description" content={meta.description} />
58+
<meta property="og:title" content={`${meta.title} — Sand Garden`} />
59+
<meta property="og:description" content={meta.description} />
60+
<meta
61+
property="og:image"
62+
content={`https://sandgarden.buidlguidl.com/api/og?title=${encodeURIComponent(meta.title)}`}
63+
/>
64+
<meta name="twitter:card" content="summary_large_image" />
65+
<meta
66+
name="twitter:image"
67+
content={`https://sandgarden.buidlguidl.com/api/og?title=${encodeURIComponent(meta.title)}`}
68+
/>
5869
</Head>
5970

6071
<article className="max-w-3xl px-4 py-8">

0 commit comments

Comments
 (0)