Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
43 changes: 29 additions & 14 deletions src/components/Head.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
};
---

<!-- Global Metadata -->
Expand All @@ -45,19 +67,12 @@ const { title, description, image = "/nano.png" } = Astro.props;
<meta name="title" content={title} />
<meta name="description" content={description} />

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, Astro.url)} />

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, Astro.url)} />
<!-- OpenGraph Meta Tags -->
{ogData ? (
<OpenGraphMeta data={ogData} />
) : (
<OpenGraphMeta data={defaultOgData} />
)}

<ClientRouter />

Expand Down
51 changes: 51 additions & 0 deletions src/components/OpenGraphMeta.astro
Original file line number Diff line number Diff line change
@@ -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,
};
---

<SEO
title={data.title}
description={data.description}
openGraph={openGraph}
twitter={twitter}
/>
27 changes: 24 additions & 3 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/layouts/PageLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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;
---

<!doctype html>
<html lang="en">
<head>
<Head title={`${title} | ${SITE.NAME}`} description={description} />
<Head title={`${title} | ${SITE.NAME}`} description={description} ogData={ogData} />
</head>
<body>
<Header />
Expand Down
Loading