Skip to content

Commit fefe08c

Browse files
og images for pages
1 parent fac3deb commit fefe08c

File tree

12 files changed

+198
-4
lines changed

12 files changed

+198
-4
lines changed

app/api/og/route.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { ImageResponse } from '@vercel/og'
2+
3+
import { ogImageSchema } from '@/lib/validations/og'
4+
import siteMetadata from '@/data/siteMetadata'
5+
6+
export const runtime = 'edge'
7+
8+
const interRegular = fetch(
9+
new URL('../../../assets/fonts/Inter-Regular.ttf', import.meta.url)
10+
).then((res) => res.arrayBuffer())
11+
12+
const interBold = fetch(
13+
new URL('../../../assets/fonts/OpenSans-ExtraBold.ttf', import.meta.url)
14+
).then((res) => res.arrayBuffer())
15+
16+
export async function GET(req: Request) {
17+
try {
18+
const fontRegular = await interRegular
19+
const fontBold = await interBold
20+
21+
const url = new URL(req.url)
22+
const values = ogImageSchema.parse(Object.fromEntries(url.searchParams))
23+
const heading =
24+
values.heading.length > 140 ? `${values.heading.substring(0, 140)}...` : values.heading
25+
26+
const { mode } = values
27+
const paint = mode === 'dark' ? '#fff' : '#000'
28+
29+
// Dynamic font size calculation
30+
const getFontSize = (length: number) => {
31+
if (length < 40) return 'text-[100px]'
32+
if (length < 80) return 'text-[80px]'
33+
if (length < 120) return 'text-[60px]'
34+
return 'text-[50px]'
35+
}
36+
37+
return new ImageResponse(
38+
(
39+
<div
40+
tw="flex relative flex-col p-12 w-full h-full items-start"
41+
style={{
42+
color: paint,
43+
background: mode === 'dark' ? 'linear-gradient(90deg, #000 0%, #111 100%)' : 'white',
44+
}}
45+
>
46+
{siteMetadata.siteLogo && (
47+
// eslint-disable-next-line @next/next/no-img-element
48+
<img
49+
src={
50+
siteMetadata.siteLogo.startsWith('/')
51+
? siteMetadata.siteUrl + siteMetadata.siteLogo
52+
: siteMetadata.siteLogo
53+
}
54+
alt="Logo"
55+
width={50}
56+
height={50}
57+
/>
58+
)}
59+
<div tw="flex flex-col flex-1 py-10">
60+
<div
61+
tw="flex text-xl uppercase font-bold tracking-tight"
62+
style={{ fontFamily: 'Inter', fontWeight: 'normal' }}
63+
>
64+
{values.type}
65+
</div>
66+
<div
67+
tw={`flex leading-[1.1] ${getFontSize(heading.length)} font-bold`}
68+
style={{
69+
fontFamily: 'Inter Bold',
70+
fontWeight: 'bold',
71+
marginLeft: '-3px',
72+
}}
73+
>
74+
{heading}
75+
</div>
76+
</div>
77+
<div tw="flex items-center w-full justify-between">
78+
<div tw="flex text-xl" style={{ fontFamily: 'Inter', fontWeight: 'normal' }}>
79+
{siteMetadata.siteUrl}
80+
</div>
81+
{siteMetadata.github && (
82+
<div
83+
tw="flex items-center text-xl"
84+
style={{ fontFamily: 'Inter', fontWeight: 'normal' }}
85+
>
86+
<svg width="32" height="32" viewBox="0 0 48 48" fill="none">
87+
<path
88+
d="M30 44v-8a9.6 9.6 0 0 0-2-7c6 0 12-4 12-11 .16-2.5-.54-4.96-2-7 .56-2.3.56-4.7 0-7 0 0-2 0-6 3-5.28-1-10.72-1-16 0-4-3-6-3-6-3-.6 2.3-.6 4.7 0 7a10.806 10.806 0 0 0-2 7c0 7 6 11 12 11a9.43 9.43 0 0 0-1.7 3.3c-.34 1.2-.44 2.46-.3 3.7v8"
89+
stroke={paint}
90+
stroke-width="2"
91+
stroke-linecap="round"
92+
stroke-linejoin="round"
93+
/>
94+
<path
95+
d="M18 36c-9.02 4-10-4-14-4"
96+
stroke={paint}
97+
stroke-width="2"
98+
stroke-linecap="round"
99+
stroke-linejoin="round"
100+
/>
101+
</svg>
102+
<div tw="flex ml-2">{siteMetadata.github.replace('https://', '')}</div>
103+
</div>
104+
)}
105+
</div>
106+
</div>
107+
),
108+
{
109+
width: 1200,
110+
height: 630,
111+
fonts: [
112+
{
113+
name: 'Inter',
114+
data: fontRegular,
115+
weight: 400,
116+
style: 'normal',
117+
},
118+
{
119+
name: 'Inter Bold',
120+
data: fontBold,
121+
weight: 700,
122+
style: 'normal',
123+
},
124+
],
125+
}
126+
)
127+
} catch (error) {
128+
return new Response(`Failed to generate image`, {
129+
status: 500,
130+
})
131+
}
132+
}

app/blog/[...slug]/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Metadata } from 'next'
1212
import siteMetadata from '@/data/siteMetadata'
1313
import { notFound } from 'next/navigation'
1414
import { getPostViews } from '@/app/actions'
15+
import { getOgImageUrl } from '@/lib/getOgImageUrl'
1516

1617
const defaultLayout = 'PostLayout'
1718
const layouts = {
@@ -38,7 +39,13 @@ export async function generateMetadata(props: {
3839
const publishedAt = new Date(post.date).toISOString()
3940
const modifiedAt = new Date(post.lastmod || post.date).toISOString()
4041
const authors = authorDetails.map((author) => author.name)
41-
let imageList = [siteMetadata.socialBanner]
42+
43+
const ogImageUrl = getOgImageUrl({
44+
heading: post.title,
45+
type: 'Blog Post',
46+
mode: 'dark',
47+
})
48+
let imageList = [ogImageUrl]
4249
if (post.images.length > 0) {
4350
imageList = post.images
4451
}

app/blog/page.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@ import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
33
import { allBlogs } from 'contentlayer/generated'
44
import { genPageMetadata } from 'app/seo'
55
import { getAllViews } from '../actions'
6+
import { getOgImageUrl } from '@/lib/getOgImageUrl'
67

78
const POSTS_PER_PAGE = 5
89

9-
export const metadata = genPageMetadata({ title: 'Blog' })
10+
export const metadata = genPageMetadata({
11+
title: 'Blog',
12+
image: getOgImageUrl({
13+
heading: 'Blog',
14+
type: 'Page',
15+
mode: 'dark',
16+
}),
17+
})
1018

1119
export default async function BlogPage() {
1220
const posts = allCoreContent(sortPosts(allBlogs.filter((blog) => blog.draft !== true)))

app/blog/page/[page]/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import ListLayout from '@/layouts/ListLayoutWithTags'
22
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
33
import { allBlogs } from 'contentlayer/generated'
44
import { getAllViews } from '@/app/actions'
5+
import { genPageMetadata } from '@/app/seo'
6+
import { getOgImageUrl } from '@/lib/getOgImageUrl'
57

68
const POSTS_PER_PAGE = 5
79

@@ -14,6 +16,15 @@ export const generateStaticParams = async () => {
1416
return paths
1517
}
1618

19+
export const metadata = genPageMetadata({
20+
title: 'Blog',
21+
image: getOgImageUrl({
22+
heading: 'Blog',
23+
type: 'Page',
24+
mode: 'dark',
25+
}),
26+
})
27+
1728
export default async function Page(props: { params: Promise<{ page: string }> }) {
1829
const params = await props.params
1930
const posts = allCoreContent(sortPosts(allBlogs.filter((blog) => blog.draft !== true)))

app/projects/page.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import projectsData from '@/data/projectsData'
22
import Card from '@/components/Card'
33
import { genPageMetadata } from 'app/seo'
4+
import { getOgImageUrl } from '@/lib/getOgImageUrl'
45

5-
export const metadata = genPageMetadata({ title: 'Projects' })
6+
export const metadata = genPageMetadata({
7+
title: 'Projects',
8+
image: getOgImageUrl({
9+
heading: 'Projects',
10+
type: 'Page',
11+
mode: 'dark',
12+
}),
13+
})
614

715
export default function Projects() {
816
return (

app/tags/[tag]/page.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { genPageMetadata } from 'app/seo'
88
import { Metadata } from 'next'
99
import { notFound } from 'next/navigation'
1010
import { getAllViews } from '@/app/actions'
11+
import { getOgImageUrl } from '@/lib/getOgImageUrl'
1112

1213
export async function generateMetadata(props: {
1314
params: Promise<{ tag: string }>
@@ -27,6 +28,11 @@ export async function generateMetadata(props: {
2728
'application/rss+xml': `${siteMetadata.siteUrl}/tags/${tag}/feed.xml`,
2829
},
2930
},
31+
image: getOgImageUrl({
32+
heading: naturalTagName,
33+
type: 'Page',
34+
mode: 'dark',
35+
}),
3036
})
3137
}
3238

assets/fonts/Inter-Bold.ttf

309 KB
Binary file not shown.

assets/fonts/Inter-Regular.ttf

303 KB
Binary file not shown.

bun.lockb

8.06 KB
Binary file not shown.

lib/getOgImageUrl.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import siteMetadata from '@/data/siteMetadata'
2+
import { OgImageSchema } from './validations/og'
3+
4+
export function getOgImageUrl(ogImageSchema: OgImageSchema) {
5+
const ogUrl = new URL(`${siteMetadata.siteUrl}/api/og`)
6+
ogUrl.searchParams.set('heading', ogImageSchema.heading)
7+
ogUrl.searchParams.set('type', ogImageSchema.type)
8+
ogUrl.searchParams.set('mode', ogImageSchema.mode)
9+
const ogImageUrl = ogUrl.toString()
10+
return ogImageUrl
11+
}

0 commit comments

Comments
 (0)