Skip to content

Commit 5d5cb85

Browse files
committed
Dynamic opengraphimage generation
1 parent 6fa6c3c commit 5d5cb85

File tree

15 files changed

+414
-68
lines changed

15 files changed

+414
-68
lines changed

apps/web-roo-code/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@roo-code/evals": "workspace:^",
1818
"@roo-code/types": "workspace:^",
1919
"@tanstack/react-query": "^5.79.0",
20+
"@vercel/og": "^0.6.2",
2021
"class-variance-authority": "^0.7.1",
2122
"clsx": "^2.1.1",
2223
"embla-carousel-auto-scroll": "^8.6.0",
59.3 KB
Loading
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { ImageResponse } from "@vercel/og"
2+
import { NextRequest } from "next/server"
3+
4+
export const runtime = "edge"
5+
6+
async function loadGoogleFont(font: string, text: string) {
7+
const url = `https://fonts.googleapis.com/css2?family=${font}&text=${encodeURIComponent(text)}`
8+
const css = await (await fetch(url)).text()
9+
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
10+
11+
if (resource && resource[1]) {
12+
const response = await fetch(resource[1])
13+
if (response.status === 200) {
14+
return await response.arrayBuffer()
15+
}
16+
}
17+
18+
throw new Error("failed to load font data")
19+
}
20+
21+
export async function GET(request: NextRequest) {
22+
const requestUrl = new URL(request.url)
23+
const { searchParams } = requestUrl
24+
25+
// Get title and description from query params
26+
const title = searchParams.get("title") || "Roo Code"
27+
const description = searchParams.get("description") || ""
28+
29+
// Combine all text that will be displayed for font loading
30+
const displayText = title + description
31+
32+
// Check if we should try to use the background image
33+
const useBackgroundImage = searchParams.get("bg") !== "false"
34+
35+
// Dynamically get the base URL from the current request
36+
// This ensures it works correctly in development, preview, and production environments
37+
const baseUrl = `${requestUrl.protocol}//${requestUrl.host}`
38+
const backgroundUrl = `${baseUrl}/og/base.png`
39+
40+
return new ImageResponse(
41+
(
42+
<div
43+
style={{
44+
width: "100%",
45+
height: "100%",
46+
display: "flex",
47+
position: "relative",
48+
// Use gradient background as default/fallback
49+
background: "linear-gradient(135deg, #1e3a5f 0%, #0f1922 50%, #1a2332 100%)",
50+
}}>
51+
{/* Optional Background Image - only render if explicitly requested */}
52+
{useBackgroundImage && (
53+
<div
54+
style={{
55+
position: "absolute",
56+
top: 0,
57+
left: 0,
58+
width: "100%",
59+
height: "100%",
60+
display: "flex",
61+
}}>
62+
{/* eslint-disable-next-line @next/next/no-img-element */}
63+
<img
64+
src={backgroundUrl}
65+
alt=""
66+
width={1200}
67+
height={630}
68+
style={{
69+
width: "100%",
70+
height: "100%",
71+
objectFit: "cover",
72+
}}
73+
/>
74+
</div>
75+
)}
76+
77+
{/* Text Content */}
78+
<div
79+
style={{
80+
position: "absolute",
81+
display: "flex",
82+
flexDirection: "column",
83+
justifyContent: "flex-end",
84+
top: "220px",
85+
left: "80px",
86+
right: "80px",
87+
bottom: "80px",
88+
}}>
89+
{/* Main Title */}
90+
<h1
91+
style={{
92+
fontSize: 70,
93+
fontWeight: 700,
94+
fontFamily: "Inter, Helvetica Neue, Helvetica, sans-serif",
95+
color: "white",
96+
lineHeight: 1.2,
97+
margin: 0,
98+
maxHeight: "2.4em",
99+
overflow: "hidden",
100+
}}>
101+
{title}
102+
</h1>
103+
104+
{/* Secondary Description */}
105+
{description && (
106+
<h2
107+
style={{
108+
fontSize: 70,
109+
fontWeight: 400,
110+
fontFamily: "Inter, Helvetica Neue, Helvetica, Arial, sans-serif",
111+
color: "rgba(255, 255, 255, 0.9)",
112+
lineHeight: 1.2,
113+
margin: 0,
114+
maxHeight: "2.4em",
115+
overflow: "hidden",
116+
}}>
117+
{description}
118+
</h2>
119+
)}
120+
</div>
121+
</div>
122+
),
123+
{
124+
width: 1200,
125+
height: 630,
126+
fonts: [
127+
{
128+
name: "Inter",
129+
data: await loadGoogleFont("Inter", displayText),
130+
style: "normal",
131+
weight: 400,
132+
},
133+
{
134+
name: "Inter",
135+
data: await loadGoogleFont("Inter:wght@700", displayText),
136+
style: "normal",
137+
weight: 700,
138+
},
139+
],
140+
// Cache for 7 days in production, 3 seconds in development
141+
headers: {
142+
"Cache-Control":
143+
process.env.NODE_ENV === "production"
144+
? "public, max-age=604800, s-maxage=604800, stale-while-revalidate=86400"
145+
: "public, max-age=3, s-maxage=3",
146+
},
147+
},
148+
)
149+
}

apps/web-roo-code/src/app/cloud/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import type { Metadata } from "next"
1616
import { Button } from "@/components/ui"
1717
import { AnimatedBackground } from "@/components/homepage"
1818
import { SEO } from "@/lib/seo"
19+
import { ogImageUrl } from "@/lib/og"
1920
import { EXTERNAL_LINKS } from "@/lib/constants"
2021
import Image from "next/image"
2122

2223
const TITLE = "Roo Code Cloud"
2324
const DESCRIPTION =
2425
"Roo Code Cloud gives you and your team the tools to take AI-coding to the next level with cloud agents, remote control, and more."
26+
const OG_DESCRIPTION = "Go way beyond the IDE"
2527
const PATH = "/cloud"
26-
const OG_IMAGE = SEO.ogImage
2728

2829
export const metadata: Metadata = {
2930
title: TITLE,
@@ -38,10 +39,10 @@ export const metadata: Metadata = {
3839
siteName: SEO.name,
3940
images: [
4041
{
41-
url: OG_IMAGE.url,
42-
width: OG_IMAGE.width,
43-
height: OG_IMAGE.height,
44-
alt: OG_IMAGE.alt,
42+
url: ogImageUrl(TITLE, OG_DESCRIPTION),
43+
width: 1200,
44+
height: 630,
45+
alt: TITLE,
4546
},
4647
],
4748
locale: SEO.locale,
@@ -51,7 +52,7 @@ export const metadata: Metadata = {
5152
card: SEO.twitterCard,
5253
title: TITLE,
5354
description: DESCRIPTION,
54-
images: [OG_IMAGE.url],
55+
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
5556
},
5657
keywords: [...SEO.keywords, "cloud", "subscription", "cloud agents", "AI cloud development"],
5758
}

apps/web-roo-code/src/app/enterprise/page.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import { ContactForm } from "@/components/enterprise/contact-form"
77
import { EXTERNAL_LINKS } from "@/lib/constants"
88
import type { Metadata } from "next"
99
import { SEO } from "@/lib/seo"
10+
import { ogImageUrl } from "@/lib/og"
1011

11-
const TITLE = "Enterprise Solution"
12+
const TITLE = "Roo Code Cloud Enterprise"
1213
const DESCRIPTION =
1314
"The control-plane for AI-powered software development. Gain visibility, governance, and control over your AI coding initiatives."
15+
const OG_DESCRIPTION = "The control-plane for AI-powered software development"
1416
const PATH = "/enterprise"
15-
const OG_IMAGE = SEO.ogImage
1617

1718
export const metadata: Metadata = {
1819
title: TITLE,
@@ -27,10 +28,10 @@ export const metadata: Metadata = {
2728
siteName: SEO.name,
2829
images: [
2930
{
30-
url: OG_IMAGE.url,
31-
width: OG_IMAGE.width,
32-
height: OG_IMAGE.height,
33-
alt: OG_IMAGE.alt,
31+
url: ogImageUrl(TITLE, OG_DESCRIPTION),
32+
width: 1200,
33+
height: 630,
34+
alt: TITLE,
3435
},
3536
],
3637
locale: SEO.locale,
@@ -40,7 +41,7 @@ export const metadata: Metadata = {
4041
card: SEO.twitterCard,
4142
title: TITLE,
4243
description: DESCRIPTION,
43-
images: [OG_IMAGE.url],
44+
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
4445
},
4546
keywords: [
4647
...SEO.keywords,

apps/web-roo-code/src/app/evals/page.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next"
22

33
import { getEvalRuns } from "@/actions/evals"
44
import { SEO } from "@/lib/seo"
5+
import { ogImageUrl } from "@/lib/og"
56

67
import { Evals } from "./evals"
78

@@ -10,13 +11,8 @@ export const dynamic = "force-dynamic"
1011

1112
const TITLE = "Evals"
1213
const DESCRIPTION = "Explore quantitative evals of LLM coding skills across tasks and providers."
14+
const OG_DESCRIPTION = "Quantitative evals of LLM coding skills"
1315
const PATH = "/evals"
14-
const IMAGE = {
15-
url: "https://i.imgur.com/ijP7aZm.png",
16-
width: 1954,
17-
height: 1088,
18-
alt: "Roo Code Evals – LLM coding benchmarks",
19-
}
2016

2117
export const metadata: Metadata = {
2218
title: TITLE,
@@ -29,15 +25,22 @@ export const metadata: Metadata = {
2925
description: DESCRIPTION,
3026
url: `${SEO.url}${PATH}`,
3127
siteName: SEO.name,
32-
images: [IMAGE],
28+
images: [
29+
{
30+
url: ogImageUrl(TITLE, OG_DESCRIPTION),
31+
width: 1200,
32+
height: 630,
33+
alt: TITLE,
34+
},
35+
],
3336
locale: SEO.locale,
3437
type: "website",
3538
},
3639
twitter: {
3740
card: SEO.twitterCard,
3841
title: TITLE,
3942
description: DESCRIPTION,
40-
images: [IMAGE.url],
43+
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
4144
},
4245
keywords: [...SEO.keywords, "benchmarks", "LLM evals", "coding evaluations", "model comparison"],
4346
}

apps/web-roo-code/src/app/layout.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react"
22
import type { Metadata } from "next"
33
import { Inter } from "next/font/google"
44
import { SEO } from "@/lib/seo"
5+
import { ogImageUrl } from "@/lib/og"
56
import { CookieConsentWrapper } from "@/components/CookieConsentWrapper"
67

78
import { Providers } from "@/components/providers"
@@ -12,6 +13,9 @@ import "./globals.css"
1213

1314
const inter = Inter({ subsets: ["latin"] })
1415

16+
const OG_TITLE = "Meet Roo Code"
17+
const OG_DESCRIPTION = "The AI dev team that gets things done."
18+
1519
export const metadata: Metadata = {
1620
metadataBase: new URL(SEO.url),
1721
title: {
@@ -51,10 +55,10 @@ export const metadata: Metadata = {
5155
siteName: SEO.name,
5256
images: [
5357
{
54-
url: SEO.ogImage.url,
55-
width: SEO.ogImage.width,
56-
height: SEO.ogImage.height,
57-
alt: SEO.ogImage.alt,
58+
url: ogImageUrl(OG_TITLE, OG_DESCRIPTION),
59+
width: 1200,
60+
height: 630,
61+
alt: OG_TITLE,
5862
},
5963
],
6064
locale: SEO.locale,
@@ -64,7 +68,7 @@ export const metadata: Metadata = {
6468
card: SEO.twitterCard,
6569
title: SEO.title,
6670
description: SEO.description,
67-
images: [SEO.ogImage.url],
71+
images: [ogImageUrl(OG_TITLE, OG_DESCRIPTION)],
6872
},
6973
robots: {
7074
index: true,

apps/web-roo-code/src/app/legal/cookies/page.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Metadata } from "next"
22
import { SEO } from "@/lib/seo"
3+
import { ogImageUrl } from "@/lib/og"
34

4-
const TITLE = "Cookie Policy"
5+
const TITLE = "Our Cookie Policy"
56
const DESCRIPTION = "Learn about how Roo Code uses cookies to enhance your experience and provide our services."
7+
const OG_DESCRIPTION = ""
68
const PATH = "/legal/cookies"
7-
const OG_IMAGE = SEO.ogImage
89

910
export const metadata: Metadata = {
1011
title: TITLE,
@@ -19,10 +20,10 @@ export const metadata: Metadata = {
1920
siteName: SEO.name,
2021
images: [
2122
{
22-
url: OG_IMAGE.url,
23-
width: OG_IMAGE.width,
24-
height: OG_IMAGE.height,
25-
alt: OG_IMAGE.alt,
23+
url: ogImageUrl(TITLE, OG_DESCRIPTION),
24+
width: 1200,
25+
height: 630,
26+
alt: TITLE,
2627
},
2728
],
2829
locale: SEO.locale,
@@ -32,7 +33,7 @@ export const metadata: Metadata = {
3233
card: SEO.twitterCard,
3334
title: TITLE,
3435
description: DESCRIPTION,
35-
images: [OG_IMAGE.url],
36+
images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
3637
},
3738
keywords: [...SEO.keywords, "cookies", "privacy", "tracking", "analytics"],
3839
}

0 commit comments

Comments
 (0)