-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add og image gen * feat: add og imgae gen * chore: self review
- Loading branch information
1 parent
5b8ed82
commit 610b345
Showing
17 changed files
with
791 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// ./app/utils/createOGImage.server.tsx | ||
|
||
import { Resvg } from "@resvg/resvg-js"; | ||
import type { SatoriOptions } from "satori"; | ||
import satori from "satori"; | ||
|
||
const OG_IMAGE_HEIGHT = 630; | ||
const OG_IMAGE_WIDTH = 1200; | ||
|
||
function getRegularSans(baseUrl: string) { | ||
return fetch(new URL(`${baseUrl}/fonts/Inter-Regular.ttf`)).then( | ||
async (res) => { | ||
return res.arrayBuffer(); | ||
}, | ||
); | ||
} | ||
|
||
async function getBaseOptions( | ||
requestUrl: string, | ||
partial: Partial<SatoriOptions> = {}, | ||
): Promise<SatoriOptions> { | ||
const fontSansData = await getRegularSans(requestUrl); | ||
|
||
return { | ||
width: OG_IMAGE_WIDTH, | ||
height: OG_IMAGE_HEIGHT, | ||
fonts: [ | ||
{ | ||
name: "Inter", | ||
data: fontSansData, | ||
weight: 400, | ||
style: "normal", | ||
}, | ||
], | ||
...partial, | ||
}; | ||
} | ||
|
||
export async function composeAuthorOGImage(params: { | ||
name: string; | ||
requestUrl: string; | ||
}) { | ||
const { name, requestUrl } = params; | ||
|
||
// Design the image and generate an SVG with "satori" | ||
const svg = await satori( | ||
<div | ||
style={{ | ||
width: OG_IMAGE_WIDTH, | ||
height: OG_IMAGE_HEIGHT, | ||
background: "linear-gradient(to bottom left, #208368, #000)", | ||
color: "white", | ||
fontFamily: "Inter", | ||
fontSize: 80, | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
gap: 20, | ||
textAlign: "center", | ||
padding: 40, | ||
}} | ||
> | ||
{name} | ||
</div>, | ||
await getBaseOptions(requestUrl), | ||
); | ||
|
||
return new Resvg(svg).render().asPng(); | ||
} | ||
|
||
export async function composePackageOGImage(params: { | ||
name: string; | ||
requestUrl: string; | ||
}) { | ||
const { name, requestUrl } = params; | ||
|
||
// Design the image and generate an SVG with "satori" | ||
const svg = await satori( | ||
<div | ||
style={{ | ||
width: OG_IMAGE_WIDTH, | ||
height: OG_IMAGE_HEIGHT, | ||
background: "linear-gradient(to bottom left, #5151cd, #000)", | ||
color: "white", | ||
fontFamily: "Inter", | ||
fontSize: 80, | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
gap: 20, | ||
textAlign: "center", | ||
padding: 40, | ||
}} | ||
> | ||
{name} | ||
</div>, | ||
await getBaseOptions(requestUrl), | ||
); | ||
|
||
return new Resvg(svg).render().asPng(); | ||
} | ||
|
||
export async function composeNewsArticleOGImage(params: { | ||
headline: string; | ||
subline?: string; | ||
requestUrl: string; | ||
}) { | ||
const { headline, subline, requestUrl } = params; | ||
|
||
// Design the image and generate an SVG with "satori" | ||
const svg = await satori( | ||
<div | ||
style={{ | ||
width: OG_IMAGE_WIDTH, | ||
height: OG_IMAGE_HEIGHT, | ||
background: "linear-gradient(to top left, #953ea3, #2f265f,#000 )", | ||
color: "white", | ||
fontFamily: "Inter", | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
gap: 20, | ||
textAlign: "center", | ||
padding: 40, | ||
}} | ||
> | ||
<span style={{ fontSize: 80 }}>{headline}</span> | ||
{subline ? ( | ||
<span style={{ fontSize: 60, opacity: 80 }}>{subline}</span> | ||
) : null} | ||
</div>, | ||
await getBaseOptions(requestUrl), | ||
); | ||
|
||
return new Resvg(svg).render().asPng(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { LoaderFunctionArgs } from "@remix-run/node"; | ||
import { composeAuthorOGImage } from "../modules/meta-og-image.server"; | ||
import { ENV } from "../data/env"; | ||
import { addDays, getSeconds } from "date-fns"; | ||
import { authorSlugSchema } from "../data/author.shape"; | ||
|
||
export const loader = async ({ request, params }: LoaderFunctionArgs) => { | ||
const { origin } = new URL(request.url); | ||
const { authorId } = params; | ||
|
||
const parsedId = authorSlugSchema.safeParse(authorId); | ||
if (parsedId.error) { | ||
throw new Response(null, { | ||
status: 400, | ||
statusText: "Valid author ID is required", | ||
}); | ||
} | ||
|
||
const png = await composeAuthorOGImage({ | ||
name: encodeURIComponent(parsedId.data), | ||
requestUrl: origin, | ||
}); | ||
|
||
// Respond with the PNG buffer | ||
return new Response(png, { | ||
status: 200, | ||
headers: { | ||
// Tell the browser the response is an image | ||
"Content-Type": "image/png", | ||
// Tip: You might want to heavily cache the response in production | ||
"cache-control": | ||
ENV.NODE_ENV === "production" | ||
? `public, immutable, no-transform, max-age=${getSeconds(addDays(new Date(), 365))}` | ||
: "no-cache", | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { LoaderFunctionArgs } from "@remix-run/node"; | ||
import { composePackageOGImage } from "../modules/meta-og-image.server"; | ||
import { ENV } from "../data/env"; | ||
import { addDays, getSeconds } from "date-fns"; | ||
import { packageSlugSchema } from "../data/package.shape"; | ||
|
||
export const loader = async ({ request, params }: LoaderFunctionArgs) => { | ||
const { origin } = new URL(request.url); | ||
const { packageId } = params; | ||
|
||
const parsedId = packageSlugSchema.safeParse(packageId); | ||
if (parsedId.error) { | ||
throw new Response(null, { | ||
status: 400, | ||
statusText: "Valid package ID is required", | ||
}); | ||
} | ||
|
||
const png = await composePackageOGImage({ | ||
name: encodeURIComponent(parsedId.data), | ||
requestUrl: origin, | ||
}); | ||
|
||
// Respond with the PNG buffer | ||
return new Response(png, { | ||
status: 200, | ||
headers: { | ||
// Tell the browser the response is an image | ||
"Content-Type": "image/png", | ||
// Tip: You might want to heavily cache the response in production | ||
"cache-control": | ||
ENV.NODE_ENV === "production" | ||
? `public, immutable, no-transform, max-age=${getSeconds(addDays(new Date(), 365))}` | ||
: "no-cache", | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.