Skip to content

Commit 93a4c9a

Browse files
committed
Add dynamic OG images for docs pages
1 parent 26906f2 commit 93a4c9a

2 files changed

Lines changed: 115 additions & 1 deletion

File tree

src/components/Layout/Page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ export function Page({
129129
title={title}
130130
titleForTitleTag={meta.titleForTitleTag}
131131
isHomePage={isHomePage}
132-
image={`/images/og-` + section + '.png'}
132+
image={
133+
isHomePage || !title
134+
? `/images/og-` + section + '.png'
135+
: `/api/og?title=${encodeURIComponent(
136+
title
137+
)}&section=${encodeURIComponent(section)}`
138+
}
133139
searchOrder={searchOrder}
134140
/>
135141
{(isHomePage || isBlogIndex) && (

src/pages/api/og.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {ImageResponse} from 'next/og';
9+
import type {NextRequest} from 'next/server';
10+
11+
export const config = {
12+
runtime: 'edge',
13+
};
14+
15+
const SECTION_LABELS: Record<string, string> = {
16+
learn: 'Learn React',
17+
reference: 'API Reference',
18+
community: 'Community',
19+
blog: 'Blog',
20+
};
21+
22+
// Satori doesn't support woff2, so the site fonts can't be reused here.
23+
// Source Code Pro matches the code-styled titles used across the docs.
24+
const fontPromise = fetch(
25+
'https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-Semibold.ttf'
26+
).then((res) => res.arrayBuffer());
27+
28+
export default async function handler(request: NextRequest) {
29+
const {searchParams} = new URL(request.url);
30+
const title = searchParams.get('title')?.slice(0, 80) ?? 'react.dev';
31+
const section = searchParams.get('section') ?? '';
32+
const label = SECTION_LABELS[section] ?? 'react.dev';
33+
const fontData = await fontPromise;
34+
35+
return new ImageResponse(
36+
(
37+
<div
38+
style={{
39+
width: '100%',
40+
height: '100%',
41+
display: 'flex',
42+
flexDirection: 'column',
43+
justifyContent: 'center',
44+
padding: '80px',
45+
backgroundColor: '#23272f',
46+
}}>
47+
<div
48+
style={{
49+
display: 'flex',
50+
alignItems: 'center',
51+
gap: '20px',
52+
}}>
53+
<svg
54+
width="72"
55+
height="72"
56+
viewBox="-11.5 -10.23 23 20.46"
57+
fill="none">
58+
<circle cx="0" cy="0" r="2.05" fill="#58c4dc" />
59+
<g stroke="#58c4dc" strokeWidth="1" fill="none">
60+
<ellipse rx="11" ry="4.2" />
61+
<ellipse rx="11" ry="4.2" transform="rotate(60)" />
62+
<ellipse rx="11" ry="4.2" transform="rotate(120)" />
63+
</g>
64+
</svg>
65+
<div
66+
style={{
67+
fontSize: 36,
68+
color: '#99a1b3',
69+
textTransform: 'uppercase',
70+
letterSpacing: '0.1em',
71+
}}>
72+
{label}
73+
</div>
74+
</div>
75+
<div
76+
style={{
77+
marginTop: 48,
78+
fontSize: title.length > 24 ? 64 : 96,
79+
color: '#f6f7f9',
80+
lineHeight: 1.1,
81+
wordBreak: 'break-word',
82+
}}>
83+
{title}
84+
</div>
85+
<div
86+
style={{
87+
marginTop: 'auto',
88+
fontSize: 32,
89+
color: '#99a1b3',
90+
}}>
91+
react.dev
92+
</div>
93+
</div>
94+
),
95+
{
96+
width: 1200,
97+
height: 630,
98+
fonts: [
99+
{
100+
name: 'Source Code Pro',
101+
data: fontData,
102+
style: 'normal',
103+
weight: 600,
104+
},
105+
],
106+
}
107+
);
108+
}

0 commit comments

Comments
 (0)