Skip to content

Commit 21e024d

Browse files
committed
add custom og data for hackathons
1 parent b8f1934 commit 21e024d

File tree

1 file changed

+91
-4
lines changed

1 file changed

+91
-4
lines changed

app/api/og/hackathons/[id]/route.tsx

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { NextRequest } from 'next/server';
22
import { ImageResponse } from 'next/og';
33
import { loadFonts, createOGResponse } from '@/utils/og-image';
4-
import { getHackathon } from '@/server/services/hackathons';
4+
import axios from 'axios';
55

66
export const runtime = 'edge';
77

@@ -13,7 +13,16 @@ export async function GET(
1313
const fonts = await loadFonts();
1414

1515
try {
16-
const hackathon = await getHackathon(id);
16+
const res = await axios.get(
17+
`${process.env.NEXTAUTH_URL}/api/hackathons/${id}`,
18+
{
19+
headers: {
20+
'Cache-Control': 'no-store',
21+
},
22+
}
23+
);
24+
25+
const hackathon = res.data || null;
1726

1827
if (!hackathon) {
1928
return createOGResponse({
@@ -24,18 +33,96 @@ export async function GET(
2433
});
2534
}
2635

36+
// If hackathon has a banner, show it as the OG image
37+
if (hackathon.banner && hackathon.banner.trim() !== '') {
38+
try {
39+
// Fetch the banner image with axios
40+
const imageResponse = await axios.get(hackathon.banner, {
41+
responseType: 'arraybuffer',
42+
headers: {
43+
'Accept': 'image/*',
44+
},
45+
});
46+
47+
const imageBuffer = imageResponse.data;
48+
49+
// Check if we got actual image data
50+
if (!imageBuffer || imageBuffer.byteLength === 0) {
51+
throw new Error('Banner image is empty');
52+
}
53+
54+
// Convert ArrayBuffer to base64 in Edge Runtime
55+
const base64 = btoa(
56+
new Uint8Array(imageBuffer).reduce(
57+
(data, byte) => data + String.fromCharCode(byte),
58+
''
59+
)
60+
);
61+
const contentType = imageResponse.headers['content-type'] || 'image/png';
62+
const imageDataUrl = `data:${contentType};base64,${base64}`;
63+
64+
return new ImageResponse(
65+
(
66+
<div
67+
style={{
68+
display: 'flex',
69+
height: '100%',
70+
width: '100%',
71+
position: 'relative',
72+
overflow: 'hidden',
73+
}}
74+
>
75+
<img
76+
src={imageDataUrl}
77+
alt={hackathon.title}
78+
style={{
79+
width: '100%',
80+
height: '100%',
81+
objectFit: 'cover',
82+
}}
83+
/>
84+
</div>
85+
),
86+
{
87+
width: 1280,
88+
height: 720,
89+
fonts: [
90+
{ name: 'Geist-Medium', data: fonts.medium, weight: 600 },
91+
{ name: 'Geist-Mono', data: fonts.regular, weight: 500 },
92+
{ name: 'Geist-Light', data: fonts.light, weight: 300 }
93+
],
94+
}
95+
);
96+
} catch (imageError: any) {
97+
// If banner fails to load, silently fallback to title/description
98+
// This is expected behavior when banner doesn't exist or fails to load
99+
if (imageError.response?.status !== 404) {
100+
console.warn('Failed to load banner image:', imageError.message);
101+
}
102+
return createOGResponse({
103+
title: hackathon.title,
104+
description: hackathon.description,
105+
path: 'hackathons',
106+
fonts
107+
});
108+
}
109+
}
110+
111+
// If no banner, show title and description
27112
return createOGResponse({
28113
title: hackathon.title,
29114
description: hackathon.description,
30115
path: 'hackathons',
31116
fonts
32117
});
33-
} catch (error) {
118+
119+
} catch (error: any) {
120+
console.error('Error fetching hackathon:', error.message || error);
34121
return createOGResponse({
35122
title: 'Hackathons',
36123
description: 'Join exciting blockchain hackathons and build the future on Avalanche',
37124
path: 'hackathons',
38125
fonts
39126
});
40127
}
41-
}
128+
}

0 commit comments

Comments
 (0)