Skip to content

Commit a738fe0

Browse files
authored
✨ feat: enhance structured data and SEO metadata across blog and index pages
1 parent b34bc88 commit a738fe0

3 files changed

Lines changed: 108 additions & 46 deletions

File tree

src/layouts/BaseLayout.astro

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,23 @@ export interface Props {
1717
publishedTime?: string;
1818
modifiedTime?: string;
1919
author?: string;
20+
readingTime?: string;
2021
}
2122
2223
const siteUrl = "https://avaabrazzaq.com";
2324
const {
2425
title,
2526
description,
2627
canonicalUrl = new URL(Astro.url.pathname, siteUrl).toString(),
27-
ogImage = new URL("/og-ai-growth-engineer.svg", siteUrl).toString(),
28+
ogImage = new URL("/og-ai-growth-engineer.png", siteUrl).toString(),
2829
ogType = "website",
2930
structuredData,
3031
noIndex = false,
3132
keywords,
3233
publishedTime,
3334
modifiedTime,
3435
author = "Avaab Razzaq",
36+
readingTime,
3537
} = Astro.props;
3638
3739
const defaultKeywords =
@@ -71,6 +73,7 @@ const currentPath = Astro.url.pathname;
7173
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
7274
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
7375
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
76+
<meta name="msapplication-TileImage" content="/pwa-192x192.png" />
7477
<link rel="canonical" href={canonicalUrl} />
7578
<link rel="sitemap" href="/sitemap-index.xml" />
7679
<link rel="alternate" type="application/rss+xml" title="Avaab Razzaq Blog RSS Feed" href="/blog/rss.xml" />
@@ -112,9 +115,23 @@ const currentPath = Astro.url.pathname;
112115

113116
{/* Twitter/X Card */}
114117
<meta name="twitter:card" content="summary_large_image" />
118+
<meta name="twitter:site" content="@itsmeAvaab" />
119+
<meta name="twitter:creator" content="@itsmeAvaab" />
115120
<meta name="twitter:title" content={title} />
116121
<meta name="twitter:description" content={description} />
117122
<meta name="twitter:image" content={ogImage} />
123+
{ogType === "article" && (
124+
<>
125+
<meta name="twitter:label1" content="Written by" />
126+
<meta name="twitter:data1" content={author} />
127+
{readingTime && (
128+
<>
129+
<meta name="twitter:label2" content="Est. reading time" />
130+
<meta name="twitter:data2" content={readingTime} />
131+
</>
132+
)}
133+
</>
134+
)}
118135

119136
<meta name="theme-color" content="#0e2a47" />
120137

src/pages/blog/[...slug].astro

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,13 @@ const { post, relatedService, relatedPosts, relatedPortfolio } = Astro.props;
7979
const { Content, headings } = await render(post);
8080
8181
const siteUrl = "https://avaabrazzaq.com";
82+
const postUrl = new URL(`/blog/${post.id}/`, siteUrl).toString();
8283
const title = `${post.data.title} | Blog - Avaab Razzaq`;
8384
const description = post.data.description;
84-
const canonicalUrl = new URL(`/blog/${post.id}/`, siteUrl).toString();
85+
const canonicalUrl = postUrl;
8586
const ogImage =
8687
post.data.image?.src ??
87-
new URL("/og-ai-growth-engineer.svg", siteUrl).toString();
88+
new URL("/og-ai-growth-engineer.png", siteUrl).toString();
8889
8990
// Format date helper
9091
function formatDate(date: Date): string {
@@ -97,7 +98,8 @@ function formatDate(date: Date): string {
9798
9899
// Calculate reading time if not in frontmatter
99100
const wordCount = (post.body ?? "").split(/\s+/).filter(Boolean).length;
100-
const readingTime = post.data.readingTime || Math.ceil(wordCount / 200);
101+
const readingTimeMinutes = post.data.readingTime || Math.ceil(wordCount / 200);
102+
const readingTimeString = `${readingTimeMinutes} ${readingTimeMinutes === 1 ? "minute" : "minutes"}`;
101103
102104
// Build the structured data graph
103105
const structuredDataGraph: Record<string, unknown>[] = [
@@ -108,27 +110,21 @@ const structuredDataGraph: Record<string, unknown>[] = [
108110
name: "Avaab Razzaq - AI Growth Engineer",
109111
description:
110112
"AI Growth Engineer specializing in web apps, AI automation, and technical SEO",
111-
publisher: { "@id": `${siteUrl}/#person` },
113+
publisher: { "@id": `${siteUrl}/#organization` },
112114
},
113115
{
114-
"@type": "WebPage",
115-
"@id": canonicalUrl,
116-
url: canonicalUrl,
117-
name: title,
118-
description: description,
119-
isPartOf: { "@id": `${siteUrl}/#website` },
120-
breadcrumb: {
121-
"@type": "BreadcrumbList",
122-
itemListElement: [
123-
{ "@type": "ListItem", position: 1, name: "Home", item: siteUrl },
124-
{
125-
"@type": "ListItem",
126-
position: 2,
127-
name: "Blog",
128-
item: `${siteUrl}/blog/`,
129-
},
130-
{ "@type": "ListItem", position: 3, name: post.data.title },
131-
],
116+
"@type": "Organization",
117+
"@id": `${siteUrl}/#organization`,
118+
name: "Avaab Razzaq - AI Growth Engineer",
119+
url: siteUrl,
120+
logo: {
121+
"@type": "ImageObject",
122+
"@id": `${siteUrl}/#logo`,
123+
url: `${siteUrl}/pwa-512x512.png`,
124+
contentUrl: `${siteUrl}/pwa-512x512.png`,
125+
width: 512,
126+
height: 512,
127+
caption: "Avaab Razzaq - AI Growth Engineer",
132128
},
133129
},
134130
{
@@ -143,29 +139,51 @@ const structuredDataGraph: Record<string, unknown>[] = [
143139
],
144140
},
145141
{
146-
"@type": "BlogPosting",
147-
"@id": `${canonicalUrl}#article`,
142+
"@type": "Article",
143+
"@id": `${postUrl}#article`,
144+
isPartOf: { "@id": postUrl },
145+
author: {
146+
name: "Avaab Razzaq",
147+
"@id": `${siteUrl}/#person`,
148+
},
148149
headline: post.data.title,
149-
description: post.data.description,
150-
author: { "@id": `${siteUrl}/#person` },
151-
publisher: { "@id": `${siteUrl}/#person` },
152150
datePublished: post.data.publishDate.toISOString(),
153-
...(post.data.updatedDate && {
154-
dateModified: post.data.updatedDate.toISOString(),
155-
}),
156-
...(post.data.image && {
157-
image: {
158-
"@type": "ImageObject",
159-
url: post.data.image.src,
160-
caption: post.data.image.alt,
161-
},
162-
}),
163-
mainEntityOfPage: { "@id": canonicalUrl },
151+
dateModified: (post.data.updatedDate ?? post.data.publishDate).toISOString(),
152+
mainEntityOfPage: { "@id": postUrl },
153+
wordCount: wordCount,
154+
publisher: { "@id": `${siteUrl}/#organization` },
155+
inLanguage: "en-US",
156+
image: post.data.image?.src ?? `${siteUrl}/og-blog.png`,
164157
keywords: post.data.tags.join(", "),
165158
articleSection: post.data.category,
159+
timeRequired: `PT${readingTimeMinutes}M`,
160+
},
161+
{
162+
"@type": "WebPage",
163+
"@id": postUrl,
164+
url: postUrl,
165+
name: post.data.title,
166+
isPartOf: { "@id": `${siteUrl}/#website` },
167+
datePublished: post.data.publishDate.toISOString(),
168+
dateModified: (post.data.updatedDate ?? post.data.publishDate).toISOString(),
169+
description: post.data.description,
170+
breadcrumb: { "@id": `${postUrl}#breadcrumb` },
166171
inLanguage: "en-US",
167-
wordCount: wordCount,
168-
timeRequired: `PT${readingTime}M`,
172+
potentialAction: [
173+
{
174+
"@type": "ReadAction",
175+
target: [postUrl],
176+
},
177+
],
178+
},
179+
{
180+
"@type": "BreadcrumbList",
181+
"@id": `${postUrl}#breadcrumb`,
182+
itemListElement: [
183+
{ "@type": "ListItem", position: 1, name: "Home", item: siteUrl },
184+
{ "@type": "ListItem", position: 2, name: "Blog", item: `${siteUrl}/blog/` },
185+
{ "@type": "ListItem", position: 3, name: post.data.title },
186+
],
169187
},
170188
];
171189
@@ -208,6 +226,7 @@ const structuredData = {
208226
publishedTime={post.data.publishDate.toISOString()}
209227
modifiedTime={post.data.updatedDate?.toISOString()}
210228
author={post.data.author}
229+
readingTime={readingTimeString}
211230
structuredData={structuredData}
212231
>
213232
<article class="min-h-screen py-12">
@@ -286,7 +305,7 @@ const structuredData = {
286305
<circle cx="12" cy="12" r="10"></circle>
287306
<polyline points="12 6 12 12 16 14"></polyline>
288307
</svg>
289-
<span>{readingTime} min read</span>
308+
<span>{readingTimeMinutes} min read</span>
290309
</div>
291310
</div>
292311

src/pages/index.astro

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,16 @@ const structuredData = {
150150
],
151151
},
152152
{
153-
"@type": "LocalBusiness",
153+
"@type": "ProfessionalService",
154154
"@id": `${siteUrl}#business`,
155155
name: "Avaab Razzaq - AI Growth Engineer",
156156
description: homepage.metaDescription,
157157
url: siteUrl,
158-
telephone: "",
159158
email: homepage.contactEmail,
160159
address: {
161160
"@type": "PostalAddress",
162161
addressLocality: "Miami",
163162
addressRegion: "Florida",
164-
postalCode: "",
165163
addressCountry: "US",
166164
},
167165
geo: {
@@ -182,6 +180,22 @@ const structuredData = {
182180
closes: "18:00",
183181
},
184182
},
183+
{
184+
"@type": "Organization",
185+
"@id": `${siteUrl}#organization`,
186+
name: "Avaab Razzaq - AI Growth Engineer",
187+
url: siteUrl,
188+
logo: {
189+
"@type": "ImageObject",
190+
"@id": `${siteUrl}#logo`,
191+
url: `${siteUrl}/pwa-512x512.png`,
192+
contentUrl: `${siteUrl}/pwa-512x512.png`,
193+
width: 512,
194+
height: 512,
195+
caption: "Avaab Razzaq - AI Growth Engineer",
196+
},
197+
sameAs: [homepage.githubUrl, linkedInUrl],
198+
},
185199
{
186200
"@type": "WebSite",
187201
"@id": `${siteUrl}#website`,
@@ -190,7 +204,19 @@ const structuredData = {
190204
description: homepage.metaDescription,
191205
inLanguage: "en-US",
192206
publisher: {
193-
"@id": `${siteUrl}#person`,
207+
"@id": `${siteUrl}#organization`,
208+
},
209+
potentialAction: {
210+
"@type": "SearchAction",
211+
target: {
212+
"@type": "EntryPoint",
213+
urlTemplate: `${siteUrl}/blog/?q={search_term_string}`,
214+
},
215+
"query-input": {
216+
"@type": "PropertyValueSpecification",
217+
valueRequired: true,
218+
valueName: "search_term_string",
219+
},
194220
},
195221
},
196222
{

0 commit comments

Comments
 (0)