Skip to content

Commit 1ac0ef9

Browse files
committed
feat: improve SEO and GEO with JSON-LD structured data
1 parent 4438333 commit 1ac0ef9

4 files changed

Lines changed: 100 additions & 5 deletions

File tree

src/components/SeoHead.astro

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
---
22
import { site as siteCfg } from "../site.config";
3-
const { title, description, canonical, ogImage } = Astro.props;
3+
const {
4+
title,
5+
description,
6+
canonical,
7+
ogImage,
8+
type = "website",
9+
publishDate,
10+
updatedDate,
11+
structuredData
12+
} = Astro.props;
413
const pageTitle = title ? `${title} — ${siteCfg.title}` : siteCfg.title;
514
const canonicalUrl = canonical ?? Astro.url.href;
615
const resolvedOgImage = ogImage ?? "/og-default.svg";
716
---
817
<meta name="description" content={description ?? siteCfg.description} />
918
<link rel="canonical" href={canonicalUrl} />
19+
<meta property="og:site_name" content={siteCfg.title} />
1020
<meta property="og:title" content={pageTitle} />
1121
<meta property="og:description" content={description ?? siteCfg.description} />
1222
<meta property="og:url" content={canonicalUrl} />
23+
<meta property="og:type" content={type} />
1324
<meta property="og:image" content={new URL(resolvedOgImage, Astro.site ?? canonicalUrl).href} />
25+
{type === "article" && publishDate && (
26+
<meta property="article:published_time" content={publishDate instanceof Date ? publishDate.toISOString() : publishDate} />
27+
)}
28+
{type === "article" && updatedDate && (
29+
<meta property="article:modified_time" content={updatedDate instanceof Date ? updatedDate.toISOString() : updatedDate} />
30+
)}
1431
<meta name="twitter:card" content="summary_large_image" />
1532
<meta name="twitter:title" content={pageTitle} />
1633
<meta name="twitter:description" content={description ?? siteCfg.description} />
1734
<meta name="twitter:image" content={new URL(resolvedOgImage, Astro.site ?? canonicalUrl).href} />
35+
{structuredData && (
36+
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
37+
)}

src/layouts/BaseLayout.astro

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,17 @@ import "../styles/global.css";
33
import Header from "../components/Header.astro";
44
import SeoHead from "../components/SeoHead.astro";
55
import { site as siteCfg } from "../site.config";
6-
const { title, description, canonical, ogImage, mainClass = "container-blog" } = Astro.props;
6+
const {
7+
title,
8+
description,
9+
canonical,
10+
ogImage,
11+
mainClass = "container-blog",
12+
type,
13+
publishDate,
14+
updatedDate,
15+
structuredData
16+
} = Astro.props;
717
const siteUrl = Astro.site ?? Astro.url;
818
const baseUrl = new URL(import.meta.env.BASE_URL, siteUrl);
919
const sitemapUrl = new URL("sitemap-index.xml", baseUrl).href;
@@ -40,7 +50,16 @@ const hasGaMeasurementId = typeof gaMeasurementId === "string" && gaMeasurementI
4050
</noscript>
4151
<link rel="sitemap" type="application/xml" href={sitemapUrl} />
4252
<title>{title ? `${title} — ${siteCfg.title}` : siteCfg.title}</title>
43-
<SeoHead title={title} description={description} canonical={canonical} ogImage={resolvedOgImage} />
53+
<SeoHead
54+
title={title}
55+
description={description}
56+
canonical={canonical}
57+
ogImage={resolvedOgImage}
58+
type={type}
59+
publishDate={publishDate}
60+
updatedDate={updatedDate}
61+
structuredData={structuredData}
62+
/>
4463
<!-- Inline theme init to prevent FOUC -->
4564
<script is:inline>
4665
(function(){

src/pages/blog/[slug].astro

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,47 @@ const ogImage = `/og/blog/${normalizePostSlug(post.slug)}.png`;
1818
1919
const dateStr = post.data.date.toISOString().slice(0, 10);
2020
const updatedStr = post.data.updated ? post.data.updated.toISOString().slice(0, 10) : null;
21+
22+
const siteUrl = Astro.site?.href ?? "https://clickin.github.io/";
23+
const postUrl = new URL(`/blog/${normalizePostSlug(post.slug)}/`, siteUrl).href;
24+
25+
const structuredData = {
26+
"@context": "https://schema.org",
27+
"@type": "BlogPosting",
28+
"headline": post.data.title,
29+
"description": post.data.description,
30+
"image": new URL(ogImage, siteUrl).href,
31+
"datePublished": post.data.date.toISOString(),
32+
"dateModified": post.data.updated?.toISOString() ?? post.data.date.toISOString(),
33+
"author": {
34+
"@type": "Person",
35+
"name": "Clickin",
36+
"url": siteUrl
37+
},
38+
"publisher": {
39+
"@type": "Organization",
40+
"name": "Clickin Devlog",
41+
"logo": {
42+
"@type": "ImageObject",
43+
"url": new URL("/favicon.png", siteUrl).href
44+
}
45+
},
46+
"mainEntityOfPage": {
47+
"@type": "WebPage",
48+
"@id": postUrl
49+
}
50+
};
2151
---
22-
<BaseLayout title={post.data.title} description={post.data.description} ogImage={ogImage} mainClass="max-w-7xl mx-auto px-4 sm:px-6">
52+
<BaseLayout
53+
title={post.data.title}
54+
description={post.data.description}
55+
ogImage={ogImage}
56+
mainClass="max-w-7xl mx-auto px-4 sm:px-6"
57+
type="article"
58+
publishDate={post.data.date}
59+
updatedDate={post.data.updated}
60+
structuredData={structuredData}
61+
>
2362
<!-- Mobile Category Navigation -->
2463
<div class="lg:hidden mb-4">
2564
<CategorySidebar activeCategoryPath={post.data.category.split(">").map(s => s.trim()).join("/")} />

src/pages/index.astro

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,25 @@ const posts = (await getCollection("posts", ({ data }) => !data.draft && data.pu
88
.slice(0, 5);
99
1010
const base = import.meta.env.BASE_URL;
11+
const siteUrl = Astro.site?.href ?? "https://clickin.github.io/";
12+
13+
const structuredData = {
14+
"@context": "https://schema.org",
15+
"@type": "WebSite",
16+
"name": "Clickin Devlog",
17+
"url": siteUrl,
18+
"description": "Developer blog — notes, write-ups, and explorations.",
19+
"author": {
20+
"@type": "Person",
21+
"name": "Clickin"
22+
}
23+
};
1124
---
12-
<BaseLayout title="Home" description="Developer blog — notes, write-ups, and explorations.">
25+
<BaseLayout
26+
title="Home"
27+
description="Developer blog — notes, write-ups, and explorations."
28+
structuredData={structuredData}
29+
>
1330
<section class="py-8">
1431
<h1 class="text-4xl sm:text-5xl font-extrabold mb-4">
1532
Welcome 👋

0 commit comments

Comments
 (0)