-
-
Notifications
You must be signed in to change notification settings - Fork 173
Add user data to open graph images #1144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,7 @@ | ||||||||||||||||||||||||
| import { ImageResponse } from "next/og"; | ||||||||||||||||||||||||
| import * as Sentry from "@sentry/nextjs"; | ||||||||||||||||||||||||
| import { Stars, Waves } from "@/components/background/background"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export const runtime = "edge"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const height = 630; | ||||||||||||||||||||||||
|
|
@@ -8,153 +11,199 @@ export async function GET(request: Request) { | |||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const { searchParams } = new URL(request.url); | ||||||||||||||||||||||||
| const origin = `${request.headers.get("x-forwarded-proto") || "http"}://${request.headers.get("host")}`; | ||||||||||||||||||||||||
| // ?title=<title> | ||||||||||||||||||||||||
| const hasTitle = searchParams.has("title"); | ||||||||||||||||||||||||
| const title = hasTitle | ||||||||||||||||||||||||
| ? searchParams.get("title")?.slice(0, 100) | ||||||||||||||||||||||||
| : "My default title"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const fontData = await fetch( | ||||||||||||||||||||||||
| new URL( | ||||||||||||||||||||||||
| "https://og-playground.vercel.app/inter-latin-ext-700-normal.woff", | ||||||||||||||||||||||||
| import.meta.url, | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| const title = searchParams.get("title"); | ||||||||||||||||||||||||
| const author = searchParams.get("author"); | ||||||||||||||||||||||||
| const readTime = searchParams.get("readTime"); | ||||||||||||||||||||||||
| const date = searchParams.get("date"); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!title || !author || !readTime || !date) { | ||||||||||||||||||||||||
| throw new Error("Missing required parameters"); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const regularFontData = await fetch( | ||||||||||||||||||||||||
| new URL("@/assets/Lato-Regular.ttf", import.meta.url), | ||||||||||||||||||||||||
| ).then((res) => res.arrayBuffer()); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const boldFontData = await fetch( | ||||||||||||||||||||||||
| new URL("@/assets/Lato-Bold.ttf", import.meta.url), | ||||||||||||||||||||||||
| ).then((res) => res.arrayBuffer()); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
Comment on lines
+24
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Suggestion: Fetch font data in parallel to improve performance Currently, the font data for regular and bold fonts is fetched sequentially, which can lead to increased response times. Fetching both fonts in parallel will optimize performance by reducing the total fetch time. Apply this diff to fetch font data in parallel: - const regularFontData = await fetch(
- new URL("@/assets/Lato-Regular.ttf", import.meta.url),
- ).then((res) => res.arrayBuffer());
-
- const boldFontData = await fetch(
- new URL("@/assets/Lato-Bold.ttf", import.meta.url),
- ).then((res) => res.arrayBuffer());
+ const [regularFontData, boldFontData] = await Promise.all([
+ fetch(new URL("@/assets/Lato-Regular.ttf", import.meta.url)).then((res) => res.arrayBuffer()),
+ fetch(new URL("@/assets/Lato-Bold.ttf", import.meta.url)).then((res) => res.arrayBuffer()),
+ ]);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||
| return new ImageResponse( | ||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| tw="flex flex-col h-full w-full justify-center" | ||||||||||||||||||||||||
| tw="flex flex-col h-full w-full" | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| padding: "0 114px", | ||||||||||||||||||||||||
| fontFamily: "'Lato'", | ||||||||||||||||||||||||
| backgroundColor: "#1d1b36", | ||||||||||||||||||||||||
| backgroundRepeat: "repeat", | ||||||||||||||||||||||||
| background: ` | ||||||||||||||||||||||||
| url('${origin}/images/og/noise.svg'), | ||||||||||||||||||||||||
| radial-gradient(circle at bottom left, rgba(255, 255, 255, 0.1), transparent 40%), | ||||||||||||||||||||||||
| radial-gradient(circle at top right, rgba(255, 255, 255, 0.1), transparent 40%) | ||||||||||||||||||||||||
| `, | ||||||||||||||||||||||||
| backgroundImage: ` | ||||||||||||||||||||||||
| url('${origin}/images/og/noise.png'), | ||||||||||||||||||||||||
| radial-gradient(circle at top left, rgba(255, 255, 255, 0.15), transparent 40%), | ||||||||||||||||||||||||
| radial-gradient(circle at top right, rgba(255, 255, 255, 0.15), transparent 40%) | ||||||||||||||||||||||||
| `, | ||||||||||||||||||||||||
| backgroundRepeat: "repeat, no-repeat, no-repeat", | ||||||||||||||||||||||||
| backgroundSize: "100px 100px, 100% 100%, 100% 100%", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| className="line1" | ||||||||||||||||||||||||
| <Waves | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| width: "1200px", | ||||||||||||||||||||||||
| height: "30px", | ||||||||||||||||||||||||
| borderTop: "1px solid #39374E", | ||||||||||||||||||||||||
| borderBottom: "1px solid #39374E", | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| top: "50px", | ||||||||||||||||||||||||
| top: 0, | ||||||||||||||||||||||||
| left: 0, | ||||||||||||||||||||||||
| width, | ||||||||||||||||||||||||
| height, | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| ></div> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| className="line2" | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <Stars | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| width: "1200px", | ||||||||||||||||||||||||
| height: "30px", | ||||||||||||||||||||||||
| borderTop: "1px solid #39374E", | ||||||||||||||||||||||||
| borderBottom: "1px solid #39374E", | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| bottom: "50px", | ||||||||||||||||||||||||
| top: 0, | ||||||||||||||||||||||||
| left: 0, | ||||||||||||||||||||||||
| width, | ||||||||||||||||||||||||
| height, | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| ></div> | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| className="line3" | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| width: "30px", | ||||||||||||||||||||||||
| height: "100%", | ||||||||||||||||||||||||
| borderRight: "1px solid #39374E", | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| left: "50px", | ||||||||||||||||||||||||
| top: "50px", | ||||||||||||||||||||||||
| left: 0, | ||||||||||||||||||||||||
| right: 0, | ||||||||||||||||||||||||
| borderTop: "1px solid rgba(255, 255, 255, 0.1)", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| ></div> | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| className="line4" | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| width: "30px", | ||||||||||||||||||||||||
| height: "100%", | ||||||||||||||||||||||||
| borderLeft: "1px solid #39374E", | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| right: "50px", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| ></div> | ||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||
| alt="waves" | ||||||||||||||||||||||||
| src={`${origin}/images/og/waves.svg`} | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| top: "0", | ||||||||||||||||||||||||
| left: "0", | ||||||||||||||||||||||||
| width: "1200", | ||||||||||||||||||||||||
| height: "630", | ||||||||||||||||||||||||
| bottom: "50px", | ||||||||||||||||||||||||
| left: 0, | ||||||||||||||||||||||||
| right: 0, | ||||||||||||||||||||||||
| borderBottom: "1px solid rgba(255, 255, 255, 0.1)", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||
| alt="stars" | ||||||||||||||||||||||||
| src={`${origin}/images/og/stars.svg`} | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| top: "0", | ||||||||||||||||||||||||
| left: "0", | ||||||||||||||||||||||||
| width: "1200", | ||||||||||||||||||||||||
| height: "630", | ||||||||||||||||||||||||
| left: "50px", | ||||||||||||||||||||||||
| top: 0, | ||||||||||||||||||||||||
| bottom: 0, | ||||||||||||||||||||||||
| width: "40px", | ||||||||||||||||||||||||
| borderLeft: "1px solid rgba(255, 255, 255, 0.1)", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||
| alt="planet" | ||||||||||||||||||||||||
| src={`${origin}/images/og/planet.svg`} | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| height: "188px", | ||||||||||||||||||||||||
| width: "188px", | ||||||||||||||||||||||||
| right: "0", | ||||||||||||||||||||||||
| top: "10px", | ||||||||||||||||||||||||
| right: "50px", | ||||||||||||||||||||||||
| top: 0, | ||||||||||||||||||||||||
| bottom: 0, | ||||||||||||||||||||||||
| width: "40px", | ||||||||||||||||||||||||
| borderRight: "1px solid rgba(255, 255, 255, 0.1)", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||
| alt="Codu Logo" | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||
| height: "53px", | ||||||||||||||||||||||||
| width: "163px", | ||||||||||||||||||||||||
| top: "114px", | ||||||||||||||||||||||||
| left: "114px", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| src="https://www.codu.co/_next/image?url=%2Fimages%2Fcodu.png&w=1920&q=75" | ||||||||||||||||||||||||
| alt="planet" | ||||||||||||||||||||||||
| tw="h-[528px] w-[528px] absolute right-[-170px] top-[-170px]" | ||||||||||||||||||||||||
| src={`${origin}/images/og/planet.png`} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| <div tw="flex relative flex-col" style={{ marginTop: "200px" }}> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| color: "white", | ||||||||||||||||||||||||
| fontSize: "52px", | ||||||||||||||||||||||||
| lineHeight: 1, | ||||||||||||||||||||||||
| fontWeight: "800", | ||||||||||||||||||||||||
| letterSpacing: "-.025em", | ||||||||||||||||||||||||
| fontFamily: "Lato", | ||||||||||||||||||||||||
| lineClamp: 3, | ||||||||||||||||||||||||
| textWrap: "balance", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {title} | ||||||||||||||||||||||||
| {/* Main content */} | ||||||||||||||||||||||||
| <div tw="flex flex-col h-full w-full px-28 py-28"> | ||||||||||||||||||||||||
| <div tw="flex flex-grow"> | ||||||||||||||||||||||||
| <img | ||||||||||||||||||||||||
| alt="Codu Logo" | ||||||||||||||||||||||||
| tw="h-10" | ||||||||||||||||||||||||
| src={`${origin}/images/codu.png`} | ||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| <div tw="flex flex-col"> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| tw="mb-8 font-bold" | ||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||
| color: "white", | ||||||||||||||||||||||||
| fontSize: "46px", | ||||||||||||||||||||||||
| lineHeight: "1.2", | ||||||||||||||||||||||||
| letterSpacing: "-0.025em", | ||||||||||||||||||||||||
| fontFamily: "Lato-Bold", | ||||||||||||||||||||||||
| display: "-webkit-box", | ||||||||||||||||||||||||
| WebkitLineClamp: "3", | ||||||||||||||||||||||||
| WebkitBoxOrient: "vertical", | ||||||||||||||||||||||||
| overflow: "hidden", | ||||||||||||||||||||||||
| textOverflow: "ellipsis", | ||||||||||||||||||||||||
| paddingBottom: "0.1em", | ||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {title} | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| <div tw="flex items-center justify-between"> | ||||||||||||||||||||||||
| <div tw="flex flex-col"> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| tw="flex text-2xl text-neutral-100" | ||||||||||||||||||||||||
| style={{ paddingBottom: "0.1em" }} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {author} | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||
| tw="text-xl text-neutral-400" | ||||||||||||||||||||||||
| style={{ paddingBottom: "0.1em" }} | ||||||||||||||||||||||||
| > | ||||||||||||||||||||||||
| {`${formatDate(date)} · ${readTime} min read`} | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| fonts: [ | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Inter Latin", | ||||||||||||||||||||||||
| data: fontData, | ||||||||||||||||||||||||
| name: "Lato", | ||||||||||||||||||||||||
| data: regularFontData, | ||||||||||||||||||||||||
| style: "normal", | ||||||||||||||||||||||||
| weight: 400, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| { | ||||||||||||||||||||||||
| name: "Lato-Bold", | ||||||||||||||||||||||||
| data: boldFontData, | ||||||||||||||||||||||||
| style: "normal", | ||||||||||||||||||||||||
| weight: 700, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||
| height, | ||||||||||||||||||||||||
| width, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| Sentry.captureException(err); | ||||||||||||||||||||||||
| return new Response(`Failed to generate the image`, { | ||||||||||||||||||||||||
| status: 500, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function formatDate(dateString: string): string { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| let date: Date; | ||||||||||||||||||||||||
| if (dateString.includes(" ")) { | ||||||||||||||||||||||||
| // Handle the specific format from the URL | ||||||||||||||||||||||||
| const [datePart, timePart] = dateString.split(" "); | ||||||||||||||||||||||||
| const [year, month, day] = datePart.split("-"); | ||||||||||||||||||||||||
| const [time] = timePart.split("."); // Remove milliseconds | ||||||||||||||||||||||||
| const isoString = `${year}-${month}-${day}T${time}Z`; | ||||||||||||||||||||||||
| date = new Date(isoString); | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| date = new Date(dateString); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (isNaN(date.getTime())) { | ||||||||||||||||||||||||
| throw new Error("Invalid date"); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+198
to
+200
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace unsafe Using the global Apply this diff to fix the issue: - if (isNaN(date.getTime())) {
+ if (Number.isNaN(date.getTime())) {📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome
|
||||||||||||||||||||||||
| return date.toLocaleString("en-US", { | ||||||||||||||||||||||||
| month: "long", | ||||||||||||||||||||||||
| day: "numeric", | ||||||||||||||||||||||||
| year: "numeric", | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||
| return ""; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
readTimeMinsis properly defined and usage is safe.The
readTimeMinsproperty is consistently defined in the codebase, ensuring that its usage does not require additional null checks. This addresses and resolves the concern about potential undefined errors.However, there is still an inconsistency between the
openGraph.imagesandtwitter.imagesproperties that needs to be addressed to maintain metadata consistency across platforms.🔗 Analysis chain
Improved Open Graph metadata with article details.
The addition of
readTime,author, anddateto the Open Graph image URL enhances the metadata, aligning with the PR objective. Good use ofencodeURIComponentfor proper URL encoding.However, there are two points to consider:
twitter.imagesproperty hasn't been updated to match the new format used inopenGraph.images. This could lead to inconsistency in metadata across platforms.Consider updating the
twitter.imagesproperty to match:readTimeMinsproperty is used without checking if it exists, which could potentially lead to errors if the property is undefined.Consider adding a null check or providing a default value:
Let's verify the existence and type of the
readTimeMinsproperty:🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 402
Script:
Length of output: 1935