feat: generate dynamic og image for blog posts (#15)

* feat: add dynamic og image using satori
* feat: add og card styling
* feat: add dynamic paths for ogTitle
* feat: add default og url
* ci: change config site url for testing purpose
* fix: update original auto-generate og image style
* refactor: move ogImage jsx inside generateOgImage
* fix: add conditions for filtering draft posts & ogImages
* build: update minor and major dependency updates
* build: update site option in config file
This commit is contained in:
Sat Naing
2022-12-28 09:09:57 +06:30
committed by GitHub
parent f115e1f31d
commit ce3f1dc4a0
5 changed files with 554 additions and 208 deletions
+382 -200
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -15,11 +15,12 @@
},
"dependencies": {
"@astrojs/rss": "^1.0.3",
"astro": "^1.6.13",
"astro": "^1.8.0",
"fuse.js": "^6.6.2",
"github-slugger": "^2.0.0",
"remark-collapse": "^0.1.2",
"remark-toc": "^8.0.1",
"satori": "^0.0.44",
"tailwindcss": "^3.2.4"
},
"devDependencies": {
@@ -33,7 +34,7 @@
"cz-conventional-changelog": "^3.3.0",
"husky": "^8.0.2",
"lint-staged": "^13.1.0",
"prettier": "^2.8.0",
"prettier": "^2.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
+4 -6
View File
@@ -14,14 +14,12 @@ export interface Props {
const { frontmatter, Content } = Astro.props.post;
const { title, author, description, ogImage, datetime, tags } = frontmatter;
const ogUrl = new URL(ogImage ? ogImage : `${title}.svg`, Astro.url.origin)
.href;
---
<Layout
title={title}
author={author}
description={description}
ogImage={ogImage}
>
<Layout title={title} author={author} description={description} ogImage={ogUrl}>
<Header />
<div class="max-w-3xl mx-auto w-full px-2 flex justify-start">
<button
+24
View File
@@ -0,0 +1,24 @@
import type { APIRoute, MarkdownInstance } from "astro";
import generateOgImage from "@utils/generateOgImage";
import type { Frontmatter } from "@types";
export const get: APIRoute = async ({ params }) => ({
body: await generateOgImage(params.ogTitle),
});
const postImportResult = import.meta.glob<MarkdownInstance<Frontmatter>>(
"../contents/**/**/*.md",
{
eager: true,
}
);
const posts = Object.values(postImportResult);
export function getStaticPaths() {
return posts
.filter(({ frontmatter }) => !frontmatter.draft)
.filter(({ frontmatter }) => !frontmatter.ogImage)
.map(({ frontmatter }) => ({
params: { ogTitle: frontmatter.title },
}));
}
+141
View File
@@ -0,0 +1,141 @@
import satori, { SatoriOptions } from "satori";
import { SITE } from "@config";
const fetchFonts = async () => {
// Regular Font
const fontFileRegular = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.regular.ttf"
);
const fontRegular: ArrayBuffer = await fontFileRegular.arrayBuffer();
// Bold Font
const fontFileBold = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.bold.ttf"
);
const fontBold: ArrayBuffer = await fontFileBold.arrayBuffer();
return { fontRegular, fontBold };
};
const { fontRegular, fontBold } = await fetchFonts();
const ogImage = (text: string) => {
return (
<div
style={{
background: "#fefbfb",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
position: "absolute",
top: "-1px",
right: "-1px",
border: "4px solid #000",
background: "#ecebeb",
opacity: "0.9",
// filter: "blur(10px)",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2.5rem",
width: "88%",
height: "80%",
}}
/>
<div
style={{
border: "4px solid #000",
background: "#fefbfb",
// boxShadow: "24px 26px 8px 0px #ccc ",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2rem",
width: "88%",
height: "80%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
margin: "20px",
width: "90%",
height: "90%",
}}
>
<p
style={{
fontSize: 72,
fontWeight: "bold",
maxHeight: "84%",
overflow: "hidden",
}}
>
{text}
</p>
<div
style={{
display: "flex",
justifyContent: "space-between",
width: "100%",
marginBottom: "8px",
fontSize: 28,
}}
>
<span>
by{" "}
<span
style={{
color: "transparent",
}}
>
"
</span>
<span style={{ overflow: "hidden", fontWeight: "bold" }}>
{SITE.author}
</span>
</span>
<span style={{ overflow: "hidden", fontWeight: "bold" }}>
{SITE.title}
</span>
</div>
</div>
</div>
</div>
);
};
const options: SatoriOptions = {
width: 1200,
height: 630,
// debug: true,
fonts: [
{
name: "IBM Plex Mono",
data: fontRegular,
weight: 400,
style: "normal",
},
{
name: "IBM Plex Mono",
data: fontBold,
weight: 600,
style: "normal",
},
],
};
const generateOgImage = async (mytext = SITE.title) =>
await satori(ogImage(mytext), options);
export default generateOgImage;