forked from t3-oss/create-t3-app
-
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: dynamic OG images for docs t3-oss#986 (t3-oss#1291)
* Prepare for OG image generation * Setup basic og image generation Need to tweak it to make it look nicer, doing this for testing quick * Dont optimize @resvg/resvg-js * Include protocol in URL for OG * Use OG route for OG images in docs * Use vercel URL for og images in meta * Cache OG responses * Changes to fetching fonts * Final OG image design * Make util for site url * Setup env for satori debug * -p doesnt include untracked files ._. * Delete unneeded assets * Make file name consistent * design 2 * Design 3 * Setup OG to use reading time + path route * Fix url on og image * Fix reading time * Use Astro site hostname * Add fonts for other languages (ar, zh-hans) * Fix linting issues * fix env debug mode * Add support for RTL languages in OG images * Remove unneeded not null assertion * Remove reading time After giving it some more thought, having a reading time doesn't make sense for docs, a blog sure but not docs. * Fix overflowing issue with long titles * Make title font smaller for larger text * Remove unused dep * Remove unused Frontmatter prop * Add reference to original place for og fonts code * Format astro config what actually changed I will never know. * Fix broken lock file * Actually fix lock file? * Remove unused reading time parameter * Use existing rtl language map --------- Co-authored-by: Christopher Ehrlich <ehrlich.christopher@gmail.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>
- Loading branch information
1 parent
544f6c4
commit e828a29
Showing
10 changed files
with
7,290 additions
and
2,864 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,113 @@ | ||
type OpenGraphProps = { | ||
title: string; | ||
description: string; | ||
imageBase: string; | ||
pageUrl: string; | ||
rtl: boolean; | ||
}; | ||
|
||
export default function OpenGraph({ | ||
title, | ||
description, | ||
imageBase, | ||
pageUrl, | ||
rtl, | ||
}: OpenGraphProps) { | ||
return ( | ||
<div | ||
style={{ | ||
display: "flex", | ||
width: "1200px", | ||
height: "630px", | ||
justifyContent: "center", | ||
alignItems: "center", | ||
gap: "3rem", | ||
flexDirection: rtl ? "row-reverse" : "row", | ||
background: | ||
"linear-gradient(180deg, rgba(48,1,113,1) 0%, rgba(17,24,39,1) 100%)", | ||
}} | ||
> | ||
<img | ||
src={`${imageBase}/images/background-pattern.svg`} | ||
style={{ | ||
position: "absolute", | ||
width: "1200px", | ||
height: "1200px", | ||
opacity: 0.15, | ||
}} | ||
/> | ||
<Logo color={"#F5F5F5"} /> | ||
<div | ||
style={{ | ||
display: "flex", | ||
flexDirection: "column", | ||
alignItems: "center", | ||
gap: "1.2rem", | ||
}} | ||
> | ||
<h1 | ||
style={{ | ||
textAlign: "center", | ||
fontSize: title.length > 15 ? "70px" : "90px", | ||
lineHeight: "5rem", | ||
fontWeight: 700, | ||
color: "#fff", | ||
maxWidth: "700px", | ||
}} | ||
> | ||
{title} | ||
</h1> | ||
<h2 | ||
style={{ | ||
color: "#F5F5F5", | ||
fontSize: "40px", | ||
fontWeight: 400, | ||
maxWidth: "700px", | ||
textAlign: "center", | ||
wordBreak: "break-word", | ||
}} | ||
> | ||
{description} | ||
</h2> | ||
</div> | ||
<h3 | ||
style={{ | ||
fontSize: "40px", | ||
color: "#c3b4fc", | ||
fontWeight: 400, | ||
position: "absolute", | ||
bottom: "20px", | ||
}} | ||
> | ||
{pageUrl} | ||
</h3> | ||
</div> | ||
); | ||
} | ||
|
||
const Logo = ({ color }: { color: string }) => ( | ||
<svg | ||
width="268" | ||
height="203" | ||
viewBox="0 0 268 203" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d="M152.981 29.4786L180.491 0.918945L0.850377 0.918945V29.4786H152.981Z" | ||
fill={color} | ||
/> | ||
<path | ||
d="M159.664 101.527L257.947 1.29251L218.808 1.29228L137.874 83.0602L159.664 101.527Z" | ||
fill={color} | ||
/> | ||
<path | ||
d="M155.638 131.857L132.692 154.803L135.035 160.378C145.494 185.262 170.104 202.762 198.823 202.762C237.023 202.762 267.99 171.795 267.99 133.595C267.99 108.277 254.171 86.3783 234.102 74.3543L228.039 70.7214L207.028 92.0006L217.746 97.6588C230.659 104.475 239.427 118.019 239.427 133.595C239.427 156.021 221.248 174.2 198.823 174.2C180.714 174.2 165.352 162.339 160.126 145.94L155.638 131.857Z" | ||
fill={color} | ||
/> | ||
<path | ||
d="M98.4934 197.078L98.4934 52.2128H69.9338L69.9338 197.078H98.4934Z" | ||
fill={color} | ||
/> | ||
</svg> | ||
); |
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,72 @@ | ||
import satori from "satori"; | ||
import OpenGraph from "../components/openGraph"; | ||
import { type APIRoute } from "astro"; | ||
import { Resvg } from "@resvg/resvg-js"; | ||
import { getFont } from "../utils/ogFont"; | ||
import { SITE_URL } from "../utils/siteUrl"; | ||
import { SITE } from "../config"; | ||
import { getIsRtlFromLangCode, getLanguageFromURL } from "../languages"; | ||
|
||
const removeEndingSlash = (str: string) => str.replace(/\/$/, ""); | ||
|
||
export const get: APIRoute = async (request) => { | ||
const params = request.url.searchParams; | ||
const title = params.get("title") ?? SITE.title; | ||
const description = params.get("description") ?? SITE.description; | ||
const pagePath = params.get("pagePath") ?? ""; | ||
|
||
// Used for most languages | ||
const inter = await getFont({ | ||
family: "Inter", | ||
weights: [400, 700] as const, | ||
}); | ||
|
||
// Used for arabic text | ||
const bonaNova = await getFont({ | ||
family: "Bona Nova", | ||
weights: [400, 700] as const, | ||
}); | ||
|
||
// Used for chinese | ||
const notoSans = await getFont({ | ||
family: "Noto Sans SC", | ||
weights: [400, 700] as const, | ||
}); | ||
|
||
const hostname = request.site?.hostname.replace(/^https?:\/\//, ""); | ||
const pageLang = getLanguageFromURL(pagePath); | ||
|
||
const svg = await satori( | ||
OpenGraph({ | ||
title, | ||
description, | ||
imageBase: SITE_URL, | ||
pageUrl: `${hostname}${removeEndingSlash(pagePath)}`, | ||
rtl: getIsRtlFromLangCode(pageLang), | ||
}), | ||
{ | ||
width: 1200, | ||
height: 630, | ||
fonts: [ | ||
{ name: "Inter", data: inter[400], weight: 400 }, | ||
{ name: "Inter", data: inter[700], weight: 700 }, | ||
{ name: "Noto Sans SC", data: notoSans[400], weight: 400 }, | ||
{ name: "Noto Sans SC", data: notoSans[700], weight: 700 }, | ||
{ name: "Bona Nova", data: bonaNova[400], weight: 400 }, | ||
{ name: "Bona Nova", data: bonaNova[700], weight: 700 }, | ||
], | ||
debug: import.meta.env.DEBUG_OG === "true" ?? false, | ||
}, | ||
); | ||
|
||
const resvg = new Resvg(svg, {}); | ||
const pngData = resvg.render(); | ||
const pngBuffer = pngData.asPng(); | ||
|
||
return new Response(pngBuffer, { | ||
headers: { | ||
"Content-Type": "image/png", | ||
"cache-control": "public, max-age=31536000, immutable", | ||
}, | ||
}); | ||
}; |
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,43 @@ | ||
/** https://github.com/juliusmarminge/jumr.dev/blob/main/app/og-image/get-fonts.ts */ | ||
|
||
export async function getFont<TWeights extends readonly number[]>({ | ||
family, | ||
weights, | ||
text, | ||
}: { | ||
family: string; | ||
weights: TWeights; | ||
text?: string; | ||
}): Promise<Record<TWeights[number], ArrayBuffer>> { | ||
const API = `https://fonts.googleapis.com/css2?family=${family}:wght@${weights.join( | ||
";", | ||
)}${text ? `&text=${encodeURIComponent(text)}` : ""}`; | ||
|
||
const css = await ( | ||
await fetch(API, { | ||
headers: { | ||
// Make sure it returns TTF. | ||
"User-Agent": | ||
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1", | ||
}, | ||
}) | ||
).text(); | ||
|
||
const fonts = css | ||
.split("@font-face {") | ||
.splice(1) | ||
.map((font) => { | ||
const u = font.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/); | ||
const w = font.match(/font-weight: (\d+)/); | ||
return u?.[1] && w?.[1] ? { url: u[1], weight: parseInt(w[1]) } : null; | ||
}) | ||
.filter( | ||
(font): font is { url: string; weight: TWeights[number] } => !!font, | ||
); | ||
|
||
const promises = fonts.map(async (font) => { | ||
const res = await fetch(font.url); | ||
return [font.weight, await res.arrayBuffer()]; | ||
}); | ||
return Object.fromEntries(await Promise.all(promises)); | ||
} |
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,3 @@ | ||
export const SITE_URL = import.meta.env.VERCEL_URL | ||
? `https://${import.meta.env.VERCEL_URL}` | ||
: "http://localhost:3000"; |