11import { ImageResponse } from "next/og" ;
2+ import * as Sentry from "@sentry/nextjs" ;
3+ import { Stars , Waves } from "@/components/background/background" ;
4+
25export const runtime = "edge" ;
36
47const height = 630 ;
@@ -8,153 +11,199 @@ export async function GET(request: Request) {
811 try {
912 const { searchParams } = new URL ( request . url ) ;
1013 const origin = `${ request . headers . get ( "x-forwarded-proto" ) || "http" } ://${ request . headers . get ( "host" ) } ` ;
11- // ?title=<title>
12- const hasTitle = searchParams . has ( "title" ) ;
13- const title = hasTitle
14- ? searchParams . get ( "title" ) ?. slice ( 0 , 100 )
15- : "My default title" ;
1614
17- const fontData = await fetch (
18- new URL (
19- "https://og-playground.vercel.app/inter-latin-ext-700-normal.woff" ,
20- import . meta. url ,
21- ) ,
15+ const title = searchParams . get ( "title" ) ;
16+ const author = searchParams . get ( "author" ) ;
17+ const readTime = searchParams . get ( "readTime" ) ;
18+ const date = searchParams . get ( "date" ) ;
19+
20+ if ( ! title || ! author || ! readTime || ! date ) {
21+ throw new Error ( "Missing required parameters" ) ;
22+ }
23+
24+ const regularFontData = await fetch (
25+ new URL ( "@/assets/Lato-Regular.ttf" , import . meta. url ) ,
26+ ) . then ( ( res ) => res . arrayBuffer ( ) ) ;
27+
28+ const boldFontData = await fetch (
29+ new URL ( "@/assets/Lato-Bold.ttf" , import . meta. url ) ,
2230 ) . then ( ( res ) => res . arrayBuffer ( ) ) ;
31+
2332 return new ImageResponse (
2433 (
2534 < div
26- tw = "flex flex-col h-full w-full justify-center "
35+ tw = "flex flex-col h-full w-full"
2736 style = { {
28- padding : "0 114px " ,
37+ fontFamily : "'Lato' " ,
2938 backgroundColor : "#1d1b36" ,
30- backgroundRepeat : "repeat" ,
31- background : `
32- url('${ origin } /images/og/noise.svg'),
33- radial-gradient(circle at bottom left, rgba(255, 255, 255, 0.1), transparent 40%),
34- radial-gradient(circle at top right, rgba(255, 255, 255, 0.1), transparent 40%)
35- ` ,
39+ backgroundImage : `
40+ url('${ origin } /images/og/noise.png'),
41+ radial-gradient(circle at top left, rgba(255, 255, 255, 0.15), transparent 40%),
42+ radial-gradient(circle at top right, rgba(255, 255, 255, 0.15), transparent 40%)
43+ ` ,
44+ backgroundRepeat : "repeat, no-repeat, no-repeat" ,
45+ backgroundSize : "100px 100px, 100% 100%, 100% 100%" ,
3646 } }
3747 >
38- < div
39- className = "line1"
48+ < Waves
4049 style = { {
41- width : "1200px" ,
42- height : "30px" ,
43- borderTop : "1px solid #39374E" ,
44- borderBottom : "1px solid #39374E" ,
4550 position : "absolute" ,
46- top : "50px" ,
51+ top : 0 ,
52+ left : 0 ,
53+ width,
54+ height,
4755 } }
48- > </ div >
49- < div
50- className = "line2"
56+ />
57+ < Stars
5158 style = { {
52- width : "1200px" ,
53- height : "30px" ,
54- borderTop : "1px solid #39374E" ,
55- borderBottom : "1px solid #39374E" ,
5659 position : "absolute" ,
57- bottom : "50px" ,
60+ top : 0 ,
61+ left : 0 ,
62+ width,
63+ height,
5864 } }
59- > </ div >
65+ / >
6066 < div
61- className = "line3"
6267 style = { {
63- width : "30px" ,
64- height : "100%" ,
65- borderRight : "1px solid #39374E" ,
6668 position : "absolute" ,
67- left : "50px" ,
69+ top : "50px" ,
70+ left : 0 ,
71+ right : 0 ,
72+ borderTop : "1px solid rgba(255, 255, 255, 0.1)" ,
6873 } }
69- > </ div >
74+ / >
7075 < div
71- className = "line4"
7276 style = { {
73- width : "30px" ,
74- height : "100%" ,
75- borderLeft : "1px solid #39374E" ,
7677 position : "absolute" ,
77- right : "50px" ,
78- } }
79- > </ div >
80- < img
81- alt = "waves"
82- src = { `${ origin } /images/og/waves.svg` }
83- style = { {
84- position : "absolute" ,
85- top : "0" ,
86- left : "0" ,
87- width : "1200" ,
88- height : "630" ,
78+ bottom : "50px" ,
79+ left : 0 ,
80+ right : 0 ,
81+ borderBottom : "1px solid rgba(255, 255, 255, 0.1)" ,
8982 } }
9083 />
91- < img
92- alt = "stars"
93- src = { `${ origin } /images/og/stars.svg` }
84+ < div
9485 style = { {
9586 position : "absolute" ,
96- top : "0" ,
97- left : "0" ,
98- width : "1200" ,
99- height : "630" ,
87+ left : "50px" ,
88+ top : 0 ,
89+ bottom : 0 ,
90+ width : "40px" ,
91+ borderLeft : "1px solid rgba(255, 255, 255, 0.1)" ,
10092 } }
10193 />
102- < img
103- alt = "planet"
104- src = { `${ origin } /images/og/planet.svg` }
94+ < div
10595 style = { {
10696 position : "absolute" ,
107- height : "188px" ,
108- width : "188px" ,
109- right : "0" ,
110- top : "10px" ,
97+ right : "50px" ,
98+ top : 0 ,
99+ bottom : 0 ,
100+ width : "40px" ,
101+ borderRight : "1px solid rgba(255, 255, 255, 0.1)" ,
111102 } }
112103 />
113-
114104 < img
115- alt = "Codu Logo"
116- style = { {
117- position : "absolute" ,
118- height : "53px" ,
119- width : "163px" ,
120- top : "114px" ,
121- left : "114px" ,
122- } }
123- src = "https://www.codu.co/_next/image?url=%2Fimages%2Fcodu.png& w = 1920 & q = 75 "
105+ alt = "planet"
106+ tw = "h-[528px] w-[528px] absolute right-[-170px] top-[-170px]"
107+ src = { `${ origin } /images/og/planet.png` }
124108 />
125- < div tw = "flex relative flex-col" style = { { marginTop : "200px" } } >
126- < div
127- style = { {
128- color : "white" ,
129- fontSize : "52px" ,
130- lineHeight : 1 ,
131- fontWeight : "800" ,
132- letterSpacing : "-.025em" ,
133- fontFamily : "Lato" ,
134- lineClamp : 3 ,
135- textWrap : "balance" ,
136- } }
137- >
138- { title }
109+ { /* Main content */ }
110+ < div tw = "flex flex-col h-full w-full px-28 py-28" >
111+ < div tw = "flex flex-grow" >
112+ < img
113+ alt = "Codu Logo"
114+ tw = "h-10"
115+ src = { `${ origin } /images/codu.png` }
116+ />
117+ </ div >
118+ < div tw = "flex flex-col" >
119+ < div
120+ tw = "mb-8 font-bold"
121+ style = { {
122+ color : "white" ,
123+ fontSize : "46px" ,
124+ lineHeight : "1.2" ,
125+ letterSpacing : "-0.025em" ,
126+ fontFamily : "Lato-Bold" ,
127+ display : "-webkit-box" ,
128+ WebkitLineClamp : "3" ,
129+ WebkitBoxOrient : "vertical" ,
130+ overflow : "hidden" ,
131+ textOverflow : "ellipsis" ,
132+ paddingBottom : "0.1em" ,
133+ } }
134+ >
135+ { title }
136+ </ div >
137+ < div tw = "flex items-center justify-between" >
138+ < div tw = "flex flex-col" >
139+ < div
140+ tw = "flex text-2xl text-neutral-100"
141+ style = { { paddingBottom : "0.1em" } }
142+ >
143+ { author }
144+ </ div >
145+ < div
146+ tw = "text-xl text-neutral-400"
147+ style = { { paddingBottom : "0.1em" } }
148+ >
149+ { `${ formatDate ( date ) } · ${ readTime } min read` }
150+ </ div >
151+ </ div >
152+ </ div >
139153 </ div >
140154 </ div >
141155 </ div >
142156 ) ,
143157 {
144158 fonts : [
145159 {
146- name : "Inter Latin" ,
147- data : fontData ,
160+ name : "Lato" ,
161+ data : regularFontData ,
162+ style : "normal" ,
163+ weight : 400 ,
164+ } ,
165+ {
166+ name : "Lato-Bold" ,
167+ data : boldFontData ,
148168 style : "normal" ,
169+ weight : 700 ,
149170 } ,
150171 ] ,
151172 height,
152173 width,
153174 } ,
154175 ) ;
155- } catch {
176+ } catch ( err ) {
177+ Sentry . captureException ( err ) ;
156178 return new Response ( `Failed to generate the image` , {
157179 status : 500 ,
158180 } ) ;
159181 }
160182}
183+
184+ function formatDate ( dateString : string ) : string {
185+ try {
186+ let date : Date ;
187+ if ( dateString . includes ( " " ) ) {
188+ // Handle the specific format from the URL
189+ const [ datePart , timePart ] = dateString . split ( " " ) ;
190+ const [ year , month , day ] = datePart . split ( "-" ) ;
191+ const [ time ] = timePart . split ( "." ) ; // Remove milliseconds
192+ const isoString = `${ year } -${ month } -${ day } T${ time } Z` ;
193+ date = new Date ( isoString ) ;
194+ } else {
195+ date = new Date ( dateString ) ;
196+ }
197+
198+ if ( isNaN ( date . getTime ( ) ) ) {
199+ throw new Error ( "Invalid date" ) ;
200+ }
201+ return date . toLocaleString ( "en-US" , {
202+ month : "long" ,
203+ day : "numeric" ,
204+ year : "numeric" ,
205+ } ) ;
206+ } catch ( error ) {
207+ return "" ;
208+ }
209+ }
0 commit comments