From 96fec27774702f5ec9608a0b48a9bb3f53b84b4e Mon Sep 17 00:00:00 2001
From: plx
Date: Tue, 19 Aug 2025 16:25:29 -0500
Subject: [PATCH] Add comprehensive OpenGraph metadata system
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add astro-seo package for structured OpenGraph support
- Create OpenGraphMeta component with Twitter Card integration
- Add opengraph utility library with Tailgraph dynamic image generation
- Update content schemas with OG override fields (ogTitle, ogDescription, ogImage, etc.)
- Integrate OpenGraph metadata across all page types:
- Blog posts (article type with author, publish date)
- Briefs (article type with categories)
- Projects (website type)
- List pages (with item counts)
- Home page
- All pages now have proper social media sharing with dynamic images
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude
---
package-lock.json | 89 ++++++++++
package.json | 1 +
src/components/Head.astro | 43 +++--
src/components/OpenGraphMeta.astro | 51 ++++++
src/content/config.ts | 27 ++-
src/layouts/PageLayout.astro | 6 +-
src/lib/opengraph.ts | 258 +++++++++++++++++++++++++++++
src/pages/blog/[...slug].astro | 5 +-
src/pages/blog/index.astro | 14 +-
src/pages/briefs/[...slug].astro | 4 +-
src/pages/briefs/[category].astro | 12 +-
src/pages/briefs/index.astro | 11 +-
src/pages/index.astro | 7 +-
src/pages/projects/[...slug].astro | 5 +-
src/pages/projects/index.astro | 12 +-
15 files changed, 517 insertions(+), 28 deletions(-)
create mode 100644 src/components/OpenGraphMeta.astro
create mode 100644 src/lib/opengraph.ts
diff --git a/package-lock.json b/package-lock.json
index 18cecec..1bcb4b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"astro": "^5.0.5",
"astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5",
+ "astro-seo": "^0.8.4",
"clsx": "^2.1.0",
"eslint": "^9.32.0",
"eslint-plugin-astro": "^1.3.1",
@@ -4018,6 +4019,94 @@
"@iconify/utils": "^2.1.30"
}
},
+ "node_modules/astro-seo": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/astro-seo/-/astro-seo-0.8.4.tgz",
+ "integrity": "sha512-Ou1vzQSXAxa0K8rtNtXNvSpYqOGEgMhh0immMxJeXmbVZac3UKCNWAoXWyOQDFYsZvBugCRSg0N1phBqPMVgCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/check": "^0.5.4"
+ }
+ },
+ "node_modules/astro-seo/node_modules/@astrojs/check": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/@astrojs/check/-/check-0.5.10.tgz",
+ "integrity": "sha512-vliHXM9cu/viGeKiksUM4mXfO816ohWtawTl2ADPgTsd4nUMjFiyAl7xFZhF34yy4hq4qf7jvK1F2PlR3b5I5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@astrojs/language-server": "^2.8.4",
+ "chokidar": "^3.5.3",
+ "fast-glob": "^3.3.1",
+ "kleur": "^4.1.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "astro-check": "dist/bin.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ }
+ },
+ "node_modules/astro-seo/node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/astro-seo/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/astro-seo/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/astro-seo/node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/astrojs-compiler-sync": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/astrojs-compiler-sync/-/astrojs-compiler-sync-1.1.1.tgz",
diff --git a/package.json b/package.json
index 49e690c..ddb1047 100644
--- a/package.json
+++ b/package.json
@@ -35,6 +35,7 @@
"astro": "^5.0.5",
"astro-expressive-code": "^0.41.3",
"astro-icon": "^1.1.5",
+ "astro-seo": "^0.8.4",
"clsx": "^2.1.0",
"eslint": "^9.32.0",
"eslint-plugin-astro": "^1.3.1",
diff --git a/src/components/Head.astro b/src/components/Head.astro
index f6e36f7..8aff2dc 100644
--- a/src/components/Head.astro
+++ b/src/components/Head.astro
@@ -11,16 +11,38 @@ import lora600 from "@fontsource/lora/files/lora-latin-600-normal.woff2";
import { ClientRouter } from "astro:transitions";
import { SITE } from "@consts";
+import OpenGraphMeta from "@components/OpenGraphMeta.astro";
+import type { OpenGraphData } from "@lib/opengraph";
+import { generateTailgraphURL } from "@lib/opengraph";
interface Props {
title: string;
description: string;
image?: string;
+ ogData?: OpenGraphData;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
-const { title, description, image = "/nano.png" } = Astro.props;
+const { title, description, image = "/nano.png", ogData } = Astro.props;
+
+// Create default OG data if not provided
+const defaultOgData: OpenGraphData = ogData || {
+ title,
+ description,
+ type: "website",
+ url: canonicalURL.toString(),
+ siteName: SITE.NAME,
+ image: image.startsWith("http") ? image : generateTailgraphURL({
+ title,
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${Astro.site}favicon-light.svg`
+ }),
+ twitter: {
+ card: "summary_large_image"
+ }
+};
---
@@ -45,19 +67,12 @@ const { title, description, image = "/nano.png" } = Astro.props;
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+{ogData ? (
+
+) : (
+
+)}
diff --git a/src/components/OpenGraphMeta.astro b/src/components/OpenGraphMeta.astro
new file mode 100644
index 0000000..3b0b43b
--- /dev/null
+++ b/src/components/OpenGraphMeta.astro
@@ -0,0 +1,51 @@
+---
+import { SEO } from "astro-seo";
+import type { OpenGraphData } from "@lib/opengraph";
+
+interface Props {
+ data: OpenGraphData;
+}
+
+const { data } = Astro.props;
+
+// Build OpenGraph tags
+const openGraph = {
+ basic: {
+ title: data.title,
+ type: data.type,
+ image: data.image || "",
+ url: data.url,
+ },
+ optional: {
+ description: data.description,
+ siteName: data.siteName,
+ },
+ image: data.image ? {
+ url: data.image,
+ alt: data.imageAlt || data.title,
+ } : undefined,
+ article: data.type === "article" && data.article ? {
+ publishedTime: data.article.publishedTime?.toISOString(),
+ modifiedTime: data.article.modifiedTime?.toISOString(),
+ authors: data.article.author ? [data.article.author] : undefined,
+ section: data.article.section,
+ } : undefined,
+};
+
+// Build Twitter Card tags
+const twitter = {
+ card: data.twitter?.card || "summary_large_image",
+ creator: data.twitter?.creator,
+ title: data.title,
+ description: data.description,
+ image: data.image,
+ imageAlt: data.imageAlt || data.title,
+};
+---
+
+
\ No newline at end of file
diff --git a/src/content/config.ts b/src/content/config.ts
index a45462e..7beacc7 100644
--- a/src/content/config.ts
+++ b/src/content/config.ts
@@ -7,7 +7,14 @@ const blog = defineCollection({
cardTitle: z.string().optional(),
description: z.string(),
date: z.coerce.date(),
- draft: z.boolean().optional()
+ draft: z.boolean().optional(),
+ // OpenGraph overrides
+ ogTitle: z.string().optional(),
+ ogDescription: z.string().optional(),
+ ogImage: z.string().optional(),
+ ogImageAlt: z.string().optional(),
+ noOgImage: z.boolean().optional(),
+ modifiedDate: z.coerce.date().optional()
}).transform((data) => ({
...data,
cardTitle: data.cardTitle ?? data.title
@@ -21,7 +28,14 @@ const briefs = defineCollection({
cardTitle: z.string().optional(),
description: z.string(),
date: z.coerce.date(),
- draft: z.boolean().optional()
+ draft: z.boolean().optional(),
+ // OpenGraph overrides
+ ogTitle: z.string().optional(),
+ ogDescription: z.string().optional(),
+ ogImage: z.string().optional(),
+ ogImageAlt: z.string().optional(),
+ noOgImage: z.boolean().optional(),
+ modifiedDate: z.coerce.date().optional()
}).transform((data) => ({
...data,
cardTitle: data.cardTitle ?? data.title
@@ -37,7 +51,14 @@ const projects = defineCollection({
date: z.coerce.date(),
draft: z.boolean().optional(),
demoURL: z.string().optional(),
- repoURL: z.string().optional()
+ repoURL: z.string().optional(),
+ // OpenGraph overrides
+ ogTitle: z.string().optional(),
+ ogDescription: z.string().optional(),
+ ogImage: z.string().optional(),
+ ogImageAlt: z.string().optional(),
+ noOgImage: z.boolean().optional(),
+ modifiedDate: z.coerce.date().optional()
}).transform((data) => ({
...data,
cardTitle: data.cardTitle ?? data.title
diff --git a/src/layouts/PageLayout.astro b/src/layouts/PageLayout.astro
index c5d2156..b9a67a7 100644
--- a/src/layouts/PageLayout.astro
+++ b/src/layouts/PageLayout.astro
@@ -3,19 +3,21 @@ import Head from "@components/Head.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import { SITE } from "@consts";
+import type { OpenGraphData } from "@lib/opengraph";
type Props = {
title: string;
description: string;
+ ogData?: OpenGraphData;
};
-const { title, description } = Astro.props;
+const { title, description, ogData } = Astro.props;
---
-
+
diff --git a/src/lib/opengraph.ts b/src/lib/opengraph.ts
new file mode 100644
index 0000000..a11ec73
--- /dev/null
+++ b/src/lib/opengraph.ts
@@ -0,0 +1,258 @@
+import type { CollectionEntry } from "astro:content";
+import { SITE } from "@consts";
+
+export interface OpenGraphData {
+ title: string;
+ description: string;
+ type: "website" | "article";
+ url: string;
+ siteName: string;
+ image?: string;
+ imageAlt?: string;
+ article?: {
+ publishedTime?: Date;
+ modifiedTime?: Date;
+ author?: string;
+ section?: string;
+ };
+ twitter?: {
+ card?: "summary" | "summary_large_image";
+ creator?: string;
+ };
+}
+
+interface TailgraphParams {
+ title: string;
+ subtitle?: string;
+ author?: string;
+ theme?: "light" | "dark";
+ backgroundImage?: string;
+ logo?: string;
+}
+
+/**
+ * Generate a Tailgraph URL for dynamic OG images
+ */
+export function generateTailgraphURL(params: TailgraphParams): string {
+ const baseURL = "https://tailgraph.com/api/v1/og";
+ const searchParams = new URLSearchParams();
+
+ searchParams.set("title", params.title);
+
+ if (params.subtitle) {
+ searchParams.set("subtitle", params.subtitle);
+ }
+
+ if (params.author) {
+ searchParams.set("author", params.author);
+ }
+
+ searchParams.set("theme", params.theme || "dark");
+
+ if (params.backgroundImage) {
+ searchParams.set("backgroundImage", params.backgroundImage);
+ }
+
+ if (params.logo) {
+ searchParams.set("logo", params.logo);
+ }
+
+ return `${baseURL}?${searchParams.toString()}`;
+}
+
+/**
+ * Get OpenGraph data for a blog post
+ */
+export function getPostOGData(
+ post: CollectionEntry<"blog">,
+ url: string,
+ siteUrl: string
+): OpenGraphData {
+ const ogTitle = post.data.ogTitle || post.data.title;
+ const ogDescription = post.data.ogDescription || post.data.description;
+
+ let ogImage = post.data.ogImage;
+ if (!ogImage && !post.data.noOgImage) {
+ ogImage = generateTailgraphURL({
+ title: post.data.cardTitle || post.data.title,
+ subtitle: post.data.date.toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "numeric"
+ }),
+ author: "Paul R. Berman",
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${siteUrl}/favicon-light.svg`
+ });
+ }
+
+ return {
+ title: ogTitle,
+ description: ogDescription,
+ type: "article",
+ url,
+ siteName: SITE.NAME,
+ image: ogImage,
+ imageAlt: post.data.ogImageAlt || `${ogTitle} - Blog Post`,
+ article: {
+ publishedTime: post.data.date,
+ modifiedTime: post.data.modifiedDate,
+ author: "Paul R. Berman",
+ section: "Blog"
+ },
+ twitter: {
+ card: "summary_large_image",
+ creator: "@plxgithub"
+ }
+ };
+}
+
+/**
+ * Get OpenGraph data for a brief
+ */
+export function getBriefOGData(
+ brief: CollectionEntry<"briefs">,
+ category: { displayName: string; titlePrefix?: string } | null,
+ url: string,
+ siteUrl: string
+): OpenGraphData {
+ const ogTitle = brief.data.ogTitle || brief.data.title;
+ const ogDescription = brief.data.ogDescription || brief.data.description;
+
+ let ogImage = brief.data.ogImage;
+ if (!ogImage && !brief.data.noOgImage) {
+ ogImage = generateTailgraphURL({
+ title: brief.data.cardTitle || brief.data.title,
+ subtitle: category?.titlePrefix || category?.displayName || "Brief",
+ author: "Paul R. Berman",
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${siteUrl}/favicon-light.svg`
+ });
+ }
+
+ return {
+ title: ogTitle,
+ description: ogDescription,
+ type: "article",
+ url,
+ siteName: SITE.NAME,
+ image: ogImage,
+ imageAlt: brief.data.ogImageAlt || `${ogTitle} - Brief`,
+ article: {
+ publishedTime: brief.data.date,
+ modifiedTime: brief.data.modifiedDate,
+ author: "Paul R. Berman",
+ section: category?.displayName || "Briefs"
+ },
+ twitter: {
+ card: "summary_large_image",
+ creator: "@plxgithub"
+ }
+ };
+}
+
+/**
+ * Get OpenGraph data for a project
+ */
+export function getProjectOGData(
+ project: CollectionEntry<"projects">,
+ url: string,
+ siteUrl: string
+): OpenGraphData {
+ const ogTitle = project.data.ogTitle || project.data.title;
+ const ogDescription = project.data.ogDescription || project.data.description;
+
+ let ogImage = project.data.ogImage;
+ if (!ogImage && !project.data.noOgImage) {
+ ogImage = generateTailgraphURL({
+ title: project.data.title,
+ subtitle: "Project",
+ author: "Paul R. Berman",
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${siteUrl}/favicon-light.svg`
+ });
+ }
+
+ return {
+ title: ogTitle,
+ description: ogDescription,
+ type: "website",
+ url,
+ siteName: SITE.NAME,
+ image: ogImage,
+ imageAlt: project.data.ogImageAlt || `${ogTitle} - Project`,
+ twitter: {
+ card: "summary_large_image",
+ creator: "@plxgithub"
+ }
+ };
+}
+
+/**
+ * Get OpenGraph data for list pages
+ */
+export function getListOGData(
+ title: string,
+ description: string,
+ pageType: "blog" | "briefs" | "projects",
+ itemCount: number,
+ url: string,
+ siteUrl: string
+): OpenGraphData {
+ const subtitle = `${itemCount} ${pageType === "blog" ? "posts" : pageType}`;
+
+ const ogImage = generateTailgraphURL({
+ title,
+ subtitle,
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${siteUrl}/favicon-light.svg`
+ });
+
+ return {
+ title: `${title} | ${SITE.NAME}`,
+ description,
+ type: "website",
+ url,
+ siteName: SITE.NAME,
+ image: ogImage,
+ imageAlt: `${title} - ${SITE.NAME}`,
+ twitter: {
+ card: "summary_large_image",
+ creator: "@plxgithub"
+ }
+ };
+}
+
+/**
+ * Get OpenGraph data for the home page
+ */
+export function getHomeOGData(
+ url: string,
+ siteUrl: string
+): OpenGraphData {
+ const ogImage = generateTailgraphURL({
+ title: SITE.NAME,
+ subtitle: "Technical writing and projects",
+ theme: "dark",
+ backgroundImage: "gradient",
+ logo: `${siteUrl}/favicon-light.svg`
+ });
+
+ return {
+ title: SITE.NAME,
+ description: "Technical writing on Swift, performance optimization, and software engineering",
+ type: "website",
+ url,
+ siteName: SITE.NAME,
+ image: ogImage,
+ imageAlt: SITE.NAME,
+ twitter: {
+ card: "summary_large_image",
+ creator: "@plxgithub"
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro
index cbddad1..38d5e1c 100644
--- a/src/pages/blog/[...slug].astro
+++ b/src/pages/blog/[...slug].astro
@@ -5,6 +5,7 @@ import Container from "@components/Container.astro";
import FormattedDate from "@components/FormattedDate.astro";
import { readingTime } from "@lib/utils";
import BackToPrev from "@components/BackToPrev.astro";
+import { getPostOGData } from "@lib/opengraph";
export async function getStaticPaths() {
const posts = (await getCollection("blog"))
@@ -19,9 +20,11 @@ type Props = CollectionEntry<"blog">;
const post = Astro.props;
const { Content } = await post.render();
+
+const ogData = getPostOGData(post, Astro.url.toString(), Astro.site?.toString() || "");
---
-
+
diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro
index 0bf1264..56337d0 100644
--- a/src/pages/blog/index.astro
+++ b/src/pages/blog/index.astro
@@ -5,6 +5,7 @@ import Container from "@components/Container.astro";
import ContentCard from "@components/ContentCard.astro";
import { getBlogCardProps } from "@lib/contentCardHelpers";
import { BLOG } from "@consts";
+import { getListOGData } from "@lib/opengraph";
const data = (await getCollection("blog"))
.filter(post => !post.data.draft)
@@ -23,10 +24,19 @@ const posts = data.reduce((acc: Acc, post) => {
return acc;
}, {});
-const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a));
+const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a));
+
+const ogData = getListOGData(
+ BLOG.TITLE,
+ BLOG.DESCRIPTION,
+ "blog",
+ data.length,
+ Astro.url.toString(),
+ Astro.site?.toString() || ""
+);
---
-
+
diff --git a/src/pages/briefs/[...slug].astro b/src/pages/briefs/[...slug].astro
index be7d456..d17d159 100644
--- a/src/pages/briefs/[...slug].astro
+++ b/src/pages/briefs/[...slug].astro
@@ -5,6 +5,7 @@ import Container from "@components/Container.astro";
import FormattedDate from "@components/FormattedDate.astro";
import BackToPrev from "@components/BackToPrev.astro";
import { extractCategoryFromSlug, getCategory } from "@lib/category";
+import { getBriefOGData } from "@lib/opengraph";
import { renderInlineMarkdown } from "@lib/markdown";
export async function getStaticPaths() {
@@ -24,10 +25,11 @@ const { Content } = await brief.render();
// Extract category from the slug
const categorySlug = extractCategoryFromSlug(brief.slug);
const category = categorySlug ? getCategory(categorySlug, `src/content/briefs/${categorySlug}`) : null;
+const ogData = getBriefOGData(brief, category, Astro.url.toString(), Astro.site?.toString() || "");
const renderedTitlePrefix = category?.titlePrefix ? renderInlineMarkdown(category.titlePrefix) : null;
---
-
+
diff --git a/src/pages/briefs/[category].astro b/src/pages/briefs/[category].astro
index 220f3f0..fc6a197 100644
--- a/src/pages/briefs/[category].astro
+++ b/src/pages/briefs/[category].astro
@@ -6,6 +6,7 @@ import ContentCard from "@components/ContentCard.astro";
import { getBriefCardProps } from "@lib/contentCardHelpers";
import { getCategory, extractCategoryFromSlug } from "@lib/category";
import BackToPrev from "@components/BackToPrev.astro";
+import { getListOGData } from "@lib/opengraph";
export async function getStaticPaths() {
const allBriefs = (await getCollection("briefs"))
@@ -47,9 +48,18 @@ const renderedBriefs = await Promise.all(
return { ...item, Content };
})
);
+
+const ogData = getListOGData(
+ `${category.displayName} Briefs`,
+ category.description || `Briefs about ${category.displayName.toLowerCase()}`,
+ "briefs",
+ briefs.length,
+ Astro.url.toString(),
+ Astro.site?.toString() || ""
+);
---
-
+
diff --git a/src/pages/briefs/index.astro b/src/pages/briefs/index.astro
index 1c5104e..320d1b5 100644
--- a/src/pages/briefs/index.astro
+++ b/src/pages/briefs/index.astro
@@ -7,6 +7,7 @@ import Link from "@components/Link.astro";
import { getBriefCardProps } from "@lib/contentCardHelpers";
import { extractCategoryFromSlug, getCategory } from "@lib/category";
import { BRIEFS } from "@consts";
+import { getListOGData } from "@lib/opengraph";
const collection = (await getCollection("briefs"))
.filter(brief => !brief.data.draft)
@@ -65,9 +66,17 @@ const brief_categories = Object.keys(briefs_by_category)
);
});
+const ogData = getListOGData(
+ BRIEFS.TITLE,
+ BRIEFS.DESCRIPTION,
+ "briefs",
+ collection.length,
+ Astro.url.toString(),
+ Astro.site?.toString() || ""
+);
---
-
+
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 16bbb21..461a775 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -7,6 +7,7 @@ import { getBlogCardProps, getBriefCardProps, getProjectCardProps } from "@lib/c
import Link from "@components/Link.astro";
// import { dateRange } from "@lib/utils";
import { SITE, HOME, SOCIALS } from "@consts";
+import { getHomeOGData } from "@lib/opengraph";
const blog = (await getCollection("blog"))
.filter(post => !post.data.draft)
@@ -29,9 +30,13 @@ const briefs = await Promise.all(
})
);
+const ogData = getHomeOGData(
+ Astro.url.toString(),
+ Astro.site?.toString() || ""
+);
---
-
+
Hi! 👋🏻
diff --git a/src/pages/projects/[...slug].astro b/src/pages/projects/[...slug].astro
index 4160dfd..295c984 100644
--- a/src/pages/projects/[...slug].astro
+++ b/src/pages/projects/[...slug].astro
@@ -6,6 +6,7 @@ import FormattedDate from "@components/FormattedDate.astro";
import { readingTime } from "@lib/utils";
import BackToPrev from "@components/BackToPrev.astro";
import Link from "@components/Link.astro";
+import { getProjectOGData } from "@lib/opengraph";
export async function getStaticPaths() {
const projects = (await getCollection("projects"))
@@ -20,9 +21,11 @@ type Props = CollectionEntry<"projects">;
const project = Astro.props;
const { Content } = await project.render();
+
+const ogData = getProjectOGData(project, Astro.url.toString(), Astro.site?.toString() || "");
---
-
+
diff --git a/src/pages/projects/index.astro b/src/pages/projects/index.astro
index cfa85b5..4291e14 100644
--- a/src/pages/projects/index.astro
+++ b/src/pages/projects/index.astro
@@ -5,13 +5,23 @@ import Container from "@components/Container.astro";
import ContentCard from "@components/ContentCard.astro";
import { getProjectCardProps } from "@lib/contentCardHelpers";
import { PROJECTS } from "@consts";
+import { getListOGData } from "@lib/opengraph";
const projects = (await getCollection("projects"))
.filter(project => !project.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
+
+const ogData = getListOGData(
+ PROJECTS.TITLE,
+ PROJECTS.DESCRIPTION,
+ "projects",
+ projects.length,
+ Astro.url.toString(),
+ Astro.site?.toString() || ""
+);
---
-
+