mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-09-16 21:05:33 -04:00
glowed up open graph images
This commit is contained in:
@@ -56,68 +56,155 @@ const Image = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
|||||||
// get the post's title and image filename from its frontmatter
|
// get the post's title and image filename from its frontmatter
|
||||||
const frontmatter = await getFrontMatter(slug);
|
const frontmatter = await getFrontMatter(slug);
|
||||||
|
|
||||||
|
// template is HEAVILY inspired by https://og-new.clerkstage.dev/
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
...size,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
background: "linear-gradient(to top right, rgb(134, 239, 172), rgb(59, 130, 246), rgb(147, 51, 234))",
|
||||||
width: "100%",
|
|
||||||
background: "linear-gradient(0deg, hsla(197, 14%, 57%, 1) 0%, hsla(192, 17%, 94%, 1) 100%)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{frontmatter!.image && (
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
height: "100%",
|
||||||
display: "flex",
|
width: "100%",
|
||||||
height: "100%",
|
position: "absolute",
|
||||||
width: "100%",
|
inset: 0,
|
||||||
}}
|
filter: "brightness(100%) contrast(150%)",
|
||||||
>
|
opacity: "0.1",
|
||||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><filter id="noise" x="0" y="0"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/><feBlend mode="screen"/></filter><rect width="500" height="500" filter="url(#noise)" opacity="1"/></svg>')`,
|
||||||
<img
|
backgroundRepeat: "repeat",
|
||||||
// @ts-expect-error
|
}}
|
||||||
src={await getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter!.image}`)}
|
></div>
|
||||||
style={{ objectFit: "cover", height: "100%", width: "100%" }}
|
|
||||||
/>
|
<div
|
||||||
</div>
|
style={{
|
||||||
)}
|
height: "100%",
|
||||||
{AVATAR_PATH && (
|
width: "100%",
|
||||||
<div
|
position: "absolute",
|
||||||
style={{
|
opacity: "0.4",
|
||||||
display: "flex",
|
backgroundImage: `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><g fill-rule="evenodd" fill="#6b7280" fill-opacity="0.4"><g><path opacity="0.5" d="M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z"/><path d="M6 5V0H5v5H0v1h5v94h1V6h94V5H6z"/></g></g></svg>')`,
|
||||||
position: "absolute",
|
maskImage: "radial-gradient(rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0) 80%)",
|
||||||
left: 42,
|
}}
|
||||||
top: 42,
|
></div>
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
|
||||||
<img
|
|
||||||
// @ts-expect-error
|
|
||||||
src={await getLocalImage(AVATAR_PATH)}
|
|
||||||
style={{ height: 96, width: 96, borderRadius: "100%" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
position: "absolute",
|
paddingTop: "2rem",
|
||||||
left: 0,
|
paddingLeft: "2rem",
|
||||||
bottom: 42,
|
|
||||||
padding: "12px 20px",
|
|
||||||
margin: "0 42px",
|
|
||||||
backgroundColor: "rgba(16, 16, 16, 0.85)",
|
|
||||||
fontFamily: "Geist",
|
|
||||||
fontSize: 40,
|
|
||||||
fontWeight: 600,
|
|
||||||
lineHeight: 1.4,
|
|
||||||
letterSpacing: -0.5,
|
|
||||||
color: "#fefefe",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{frontmatter!.title}
|
<img
|
||||||
|
// @ts-expect-error
|
||||||
|
src={await getLocalImage(AVATAR_PATH)}
|
||||||
|
alt=""
|
||||||
|
style={{
|
||||||
|
width: "3rem",
|
||||||
|
height: "3rem",
|
||||||
|
borderRadius: "0.75rem",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%",
|
||||||
|
gap: "1.5rem",
|
||||||
|
paddingLeft: "2rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
rowGap: "1.5rem",
|
||||||
|
flexShrink: 0,
|
||||||
|
paddingTop: "2.5rem",
|
||||||
|
// don't wrap the title text at 50% if there's no image to leave room for
|
||||||
|
width: frontmatter!.image ? "50%" : "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexGrow: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontFamily: "Geist-Regular",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "20px",
|
||||||
|
color: "#030712",
|
||||||
|
border: "solid",
|
||||||
|
borderRadius: "100",
|
||||||
|
borderWidth: "2px",
|
||||||
|
paddingRight: "16px",
|
||||||
|
paddingLeft: "16px",
|
||||||
|
paddingTop: "5px",
|
||||||
|
paddingBottom: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Notes
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexGrow: 0,
|
||||||
|
fontFamily: "Geist-SemiBold",
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: "48px",
|
||||||
|
color: "#030712",
|
||||||
|
letterSpacing: "-0.025em",
|
||||||
|
lineHeight: "1.2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{frontmatter!.title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexGrow: 0,
|
||||||
|
fontFamily: "Geist-Regular",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "24px",
|
||||||
|
color: "#030712",
|
||||||
|
letterSpacing: "-0.025em",
|
||||||
|
lineHeight: "1.2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{new Date(frontmatter!.date).toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{frontmatter!.image && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
width: "100%", // only 50% in reality, but this gives the image the overflow look
|
||||||
|
flexGrow: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
// @ts-expect-error
|
||||||
|
src={await getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter!.image}`)}
|
||||||
|
alt=""
|
||||||
|
style={{ borderRadius: "0.75rem" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -125,12 +212,18 @@ const Image = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
|||||||
...size,
|
...size,
|
||||||
fonts: [
|
fonts: [
|
||||||
{
|
{
|
||||||
name: "Geist",
|
name: "Geist-Regular",
|
||||||
// load the Geist font directly from its npm package
|
// load the Geist font directly from its npm package
|
||||||
// IMPORTANT: include this exact path in next.config.ts under "outputFileTracingIncludes"
|
// IMPORTANT: include this exact path in next.config.ts under "outputFileTracingIncludes"
|
||||||
|
data: await readFile(join(process.cwd(), "node_modules/geist/dist/fonts/geist-sans/Geist-Regular.ttf")),
|
||||||
|
style: "normal",
|
||||||
|
weight: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Geist-SemiBold",
|
||||||
data: await readFile(join(process.cwd(), "node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf")),
|
data: await readFile(join(process.cwd(), "node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf")),
|
||||||
style: "normal",
|
style: "normal",
|
||||||
weight: 600,
|
weight: 700,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ const nextConfig: NextConfig = {
|
|||||||
"/notes/[slug]/opengraph-image": [
|
"/notes/[slug]/opengraph-image": [
|
||||||
"./notes/**/*",
|
"./notes/**/*",
|
||||||
"./app/opengraph-image.jpg",
|
"./app/opengraph-image.jpg",
|
||||||
|
"./node_modules/geist/dist/fonts/geist-sans/Geist-Regular.ttf",
|
||||||
"./node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf",
|
"./node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user