Skip to content

Commit 1010a89

Browse files
committed
fix OG rendering issue
1 parent 9a055f2 commit 1010a89

File tree

2 files changed

+62
-12
lines changed

2 files changed

+62
-12
lines changed

src/app/(rest)/blog/[slug]/opengraph-image.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises'
22
import path from 'node:path'
33
import { ImageResponse } from 'next/og'
44
import { getPost, getPostSlugs } from '~/lib/utils/posts'
5+
import { getBaseUrl } from '~/lib/utils'
56
import { Rubric } from '~/ui/logos/rubric'
67

78
export const runtime = 'nodejs'
@@ -15,6 +16,7 @@ export const size = {
1516

1617
const fontDataPromise = readFile(path.join(process.cwd(), 'src/app/fonts/matter-regular.woff'))
1718
const bannerSrcCache = new Map<string, string>()
19+
const ABSOLUTE_URL_PATTERN = /^https?:\/\//i
1820

1921
export async function generateStaticParams() {
2022
const slugs = await getPostSlugs()
@@ -96,22 +98,45 @@ export const Component = ({
9698
// Builds a cached data URI for banner images; input '/images/primitives.png' -> output 'data:image/png;base64,...'.
9799
const getBannerSrc = async (bannerImageUrl: string) => {
98100
const cached = bannerSrcCache.get(bannerImageUrl)
99-
if (cached) return cached
101+
if (cached !== undefined) return cached
100102

101103
const bannerPath = bannerImageUrl.replace(/^\//, '')
102104
const extension = path.extname(bannerPath).toLowerCase()
103-
const mimeType =
105+
const fallbackMimeType =
104106
extension === '.png'
105107
? 'image/png'
106108
: extension === '.jpg' || extension === '.jpeg'
107109
? 'image/jpeg'
108110
: extension === '.webp'
109111
? 'image/webp'
110112
: 'image/png'
111-
const bannerData = await readFile(path.join(process.cwd(), 'public', bannerPath), 'base64')
112-
const bannerSrc = `data:${mimeType};base64,${bannerData}`
113-
bannerSrcCache.set(bannerImageUrl, bannerSrc)
114-
return bannerSrc
113+
114+
try {
115+
const bannerData = await readFile(path.join(process.cwd(), 'public', bannerPath), 'base64')
116+
const bannerSrc = `data:${fallbackMimeType};base64,${bannerData}`
117+
bannerSrcCache.set(bannerImageUrl, bannerSrc)
118+
return bannerSrc
119+
} catch {
120+
// Local assets may not exist in every environment. Try HTTP as a fallback.
121+
}
122+
123+
const bannerUrl = ABSOLUTE_URL_PATTERN.test(bannerImageUrl)
124+
? bannerImageUrl
125+
: new URL(bannerImageUrl, getBaseUrl()).toString()
126+
127+
try {
128+
const response = await fetch(bannerUrl, { next: { revalidate } })
129+
if (!response.ok) throw new Error('Failed to fetch banner image')
130+
const contentType = response.headers.get('content-type')?.split(';')[0] || fallbackMimeType
131+
const bannerData = Buffer.from(await response.arrayBuffer()).toString('base64')
132+
const bannerSrc = `data:${contentType};base64,${bannerData}`
133+
bannerSrcCache.set(bannerImageUrl, bannerSrc)
134+
return bannerSrc
135+
} catch {
136+
// Keep image generation resilient even if banner lookup fails.
137+
bannerSrcCache.set(bannerImageUrl, '')
138+
return ''
139+
}
115140
}
116141

117142
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {

src/app/(rest)/blog/[slug]/twitter-image.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises'
22
import path from 'node:path'
33
import { ImageResponse } from 'next/og'
44
import { getPost, getPostSlugs } from '~/lib/utils/posts'
5+
import { getBaseUrl } from '~/lib/utils'
56
import { Rubric } from '~/ui/logos/rubric'
67

78
export const runtime = 'nodejs'
@@ -14,6 +15,7 @@ export const size = {
1415

1516
const fontDataPromise = readFile(path.join(process.cwd(), 'src/app/fonts/matter-regular.woff'))
1617
const bannerSrcCache = new Map<string, string>()
18+
const ABSOLUTE_URL_PATTERN = /^https?:\/\//i
1719

1820
export async function generateStaticParams() {
1921
const slugs = await getPostSlugs()
@@ -95,22 +97,45 @@ export const Component = ({
9597
// Builds a cached data URI for banner images; input '/images/primitives.png' -> output 'data:image/png;base64,...'.
9698
const getBannerSrc = async (bannerImageUrl: string) => {
9799
const cached = bannerSrcCache.get(bannerImageUrl)
98-
if (cached) return cached
100+
if (cached !== undefined) return cached
99101

100102
const bannerPath = bannerImageUrl.replace(/^\//, '')
101103
const extension = path.extname(bannerPath).toLowerCase()
102-
const mimeType =
104+
const fallbackMimeType =
103105
extension === '.png'
104106
? 'image/png'
105107
: extension === '.jpg' || extension === '.jpeg'
106108
? 'image/jpeg'
107109
: extension === '.webp'
108110
? 'image/webp'
109111
: 'image/png'
110-
const bannerData = await readFile(path.join(process.cwd(), 'public', bannerPath), 'base64')
111-
const bannerSrc = `data:${mimeType};base64,${bannerData}`
112-
bannerSrcCache.set(bannerImageUrl, bannerSrc)
113-
return bannerSrc
112+
113+
try {
114+
const bannerData = await readFile(path.join(process.cwd(), 'public', bannerPath), 'base64')
115+
const bannerSrc = `data:${fallbackMimeType};base64,${bannerData}`
116+
bannerSrcCache.set(bannerImageUrl, bannerSrc)
117+
return bannerSrc
118+
} catch {
119+
// Local assets may not exist in every environment. Try HTTP as a fallback.
120+
}
121+
122+
const bannerUrl = ABSOLUTE_URL_PATTERN.test(bannerImageUrl)
123+
? bannerImageUrl
124+
: new URL(bannerImageUrl, getBaseUrl()).toString()
125+
126+
try {
127+
const response = await fetch(bannerUrl, { next: { revalidate } })
128+
if (!response.ok) throw new Error('Failed to fetch banner image')
129+
const contentType = response.headers.get('content-type')?.split(';')[0] || fallbackMimeType
130+
const bannerData = Buffer.from(await response.arrayBuffer()).toString('base64')
131+
const bannerSrc = `data:${contentType};base64,${bannerData}`
132+
bannerSrcCache.set(bannerImageUrl, bannerSrc)
133+
return bannerSrc
134+
} catch {
135+
// Keep image generation resilient even if banner lookup fails.
136+
bannerSrcCache.set(bannerImageUrl, '')
137+
return ''
138+
}
114139
}
115140

116141
export default async function Response({

0 commit comments

Comments
 (0)