Skip to content

Commit 84a56af

Browse files
committed
implemented SEO
1 parent b3f6f61 commit 84a56af

File tree

10 files changed

+340
-36
lines changed

10 files changed

+340
-36
lines changed

src/components/seo/SEOTags.astro

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,118 @@
22
import { SEO } from "astro-seo";
33
import { AstroFont } from "astro-font";
44
import { SITE_URL } from "@/data/config";
5+
import { SEO_CONFIG, getPersonSchema, getWebsiteSchema } from "@/data/seo";
56
import type { HeadTags } from "@/utils/types/HeadTags";
67
78
type Props = HeadTags;
89
9-
const { title, description, noindex, og } = Astro.props;
10+
const {
11+
title,
12+
description,
13+
noindex,
14+
og,
15+
canonical,
16+
jsonLd,
17+
keywords = [],
18+
} = Astro.props;
1019
11-
const DEFAULT_TITLE_PAGE = "Arvind Singharpuria - Portfolio website";
12-
const DEFAULT_DESCRIPTION_PAGE =
13-
"A personal portfolio website containing blogs and projects.";
14-
const DEFAULT_URL_SITE = SITE_URL;
20+
const siteUrl = SITE_URL.endsWith('/') ? SITE_URL.slice(0, -1) : SITE_URL;
21+
const currentPath = Astro.url.pathname;
22+
const canonicalUrl = canonical || `${siteUrl}${currentPath}`;
23+
24+
const pageTitle = title || SEO_CONFIG.defaultTitle;
25+
const pageDescription = description || SEO_CONFIG.defaultDescription;
1526
1627
const openGraph = {
17-
title: title || og?.title || DEFAULT_TITLE_PAGE,
18-
type: og?.type || "website",
19-
image: og?.image || "/opengraph-image.jpg",
20-
alt: og?.alt || "astro portfolio template image",
21-
url: DEFAULT_URL_SITE,
22-
description: og?.description || DEFAULT_DESCRIPTION_PAGE,
28+
title: og?.title || pageTitle,
29+
type: og?.type || SEO_CONFIG.defaultOg.type,
30+
image: og?.image || SEO_CONFIG.defaultOg.image,
31+
alt: og?.alt || SEO_CONFIG.defaultOg.imageAlt,
32+
url: canonicalUrl,
33+
description: og?.description || pageDescription,
2334
};
35+
36+
// Combine default and page-specific keywords
37+
const allKeywords = [...SEO_CONFIG.defaultKeywords, ...keywords].filter(
38+
(keyword, index, self) => self.indexOf(keyword) === index
39+
);
40+
41+
// Build JSON-LD schemas
42+
const defaultSchemas = [
43+
getPersonSchema(siteUrl),
44+
getWebsiteSchema(siteUrl),
45+
];
46+
47+
const schemas = jsonLd
48+
? [...defaultSchemas, ...(Array.isArray(jsonLd) ? jsonLd : [jsonLd])]
49+
: defaultSchemas;
50+
51+
// Create full image URL
52+
const ogImageUrl = openGraph.image.startsWith('http')
53+
? openGraph.image
54+
: `${siteUrl}${openGraph.image}`;
2455
---
2556

2657
<head>
2758
<SEO
2859
charset="UTF-8"
29-
title={title || DEFAULT_TITLE_PAGE}
30-
description={description || DEFAULT_DESCRIPTION_PAGE}
60+
title={pageTitle}
61+
description={pageDescription}
3162
noindex={noindex || false}
63+
canonical={canonicalUrl}
3264
openGraph={{
3365
basic: {
3466
title: openGraph.title,
3567
type: openGraph.type,
36-
image: openGraph.image,
68+
image: ogImageUrl,
69+
url: openGraph.url,
70+
},
71+
optional: {
72+
description: openGraph.description,
73+
locale: SEO_CONFIG.locale,
74+
siteName: SEO_CONFIG.siteName,
3775
},
3876
image: {
3977
alt: openGraph.alt,
78+
width: SEO_CONFIG.defaultOg.imageWidth,
79+
height: SEO_CONFIG.defaultOg.imageHeight,
80+
type: "image/jpeg",
4081
},
4182
}}
4283
twitter={{
43-
creator: "@itsstormzz_",
84+
card: "summary_large_image",
85+
site: SEO_CONFIG.social.twitterSite,
86+
creator: SEO_CONFIG.social.twitter,
87+
title: openGraph.title,
88+
description: openGraph.description,
89+
image: ogImageUrl,
90+
imageAlt: openGraph.alt,
4491
}}
4592
extend={{
4693
link: [
47-
{ rel: "icon", href: "/favicon.svg" },
94+
{ rel: "icon", href: "/favicon.svg", type: "image/svg+xml" },
4895
{ rel: "sitemap", href: "/sitemap-index.xml" },
96+
{ rel: "canonical", href: canonicalUrl },
4997
],
5098
meta: [
5199
{ name: "viewport", content: "width=device-width, initial-scale=1" },
52100
{ name: "generator", content: Astro.generator },
53-
{
54-
name: "twitter:image",
55-
content: openGraph.image,
56-
},
57-
{ name: "twitter:card", content: "summary_large_image" },
58-
{ name: "twitter:title", content: openGraph.title },
59-
{ name: "twitter:description", content: openGraph.description },
60-
{ name: "twitter:site", content: "@itsstormzz_" },
101+
{ name: "author", content: SEO_CONFIG.author.name },
102+
{ name: "keywords", content: allKeywords.join(", ") },
103+
{ name: "theme-color", content: "#0a0a0a", media: "(prefers-color-scheme: dark)" },
104+
{ name: "theme-color", content: "#fafafa", media: "(prefers-color-scheme: light)" },
105+
{ name: "color-scheme", content: "light dark" },
106+
{ property: "og:locale", content: SEO_CONFIG.locale },
107+
{ property: "og:site_name", content: SEO_CONFIG.siteName },
61108
],
62109
}}
63110
/>
111+
112+
<!-- JSON-LD Structured Data -->
113+
{schemas.map((schema) => (
114+
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
115+
))}
116+
64117
<AstroFont
65118
config={[
66119
{

src/data/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const SITE_URL = "https://demo.maxencewolff.com/";
1+
export const SITE_URL = "https://www.arvind.app";

src/data/seo.ts

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// SEO Configuration for the portfolio site
2+
// Update these values with your actual information
3+
4+
export const SEO_CONFIG = {
5+
// Site Information
6+
siteName: "Arvind Singharpuria",
7+
siteUrl: "https://www.arvind.app",
8+
9+
// Default Meta
10+
defaultTitle: "Arvind Singharpuria | Full Stack Developer, DevOps & AI Expert",
11+
defaultDescription: "I'm Arvind Singharpuria, a full stack developer specializing in DevOps, AI prototyping, and building high-quality MVPs. Let's bring your ideas to life.",
12+
13+
// Author Information
14+
author: {
15+
name: "Arvind Singharpuria",
16+
17+
url: "https://arvind.app",
18+
twitter: "@ai_arvind",
19+
github: "Arvind644",
20+
linkedin: "arvind-singharpuria",
21+
},
22+
23+
// Social Media
24+
social: {
25+
twitter: "@ai_arvind",
26+
twitterSite: "@ai_arvind",
27+
},
28+
29+
// Default Open Graph
30+
defaultOg: {
31+
type: "website",
32+
image: "/opengraph-image.jpg",
33+
imageAlt: "Arvind Singharpuria - Full Stack Developer & DevOps Engineer",
34+
imageWidth: 1200,
35+
imageHeight: 630,
36+
},
37+
38+
// Keywords
39+
defaultKeywords: [
40+
"Full Stack Developer",
41+
"DevOps Engineer",
42+
"AI Prototyping",
43+
"MVP Development",
44+
"Web Development",
45+
"Software Engineer",
46+
"React Developer",
47+
"Node.js Developer",
48+
"Cloud Architecture",
49+
"Arvind Singharpuria",
50+
],
51+
52+
// Language & Locale
53+
locale: "en_US",
54+
language: "en",
55+
} as const;
56+
57+
// Page-specific SEO configurations
58+
export const PAGE_SEO = {
59+
home: {
60+
title: "Arvind Singharpuria | Full Stack Developer, DevOps & AI Expert",
61+
description: "I'm Arvind Singharpuria, specializing in full stack development, DevOps, and AI prototyping. Building fast, beautiful, and high-quality software solutions.",
62+
keywords: ["portfolio", "full stack developer", "devops", "AI", "software engineer"],
63+
},
64+
projects: {
65+
title: "Projects | Arvind Singharpuria",
66+
description: "Explore my portfolio of projects including web applications, MVPs, and open-source contributions. See real examples of my full stack development and DevOps work.",
67+
keywords: ["projects", "portfolio", "web applications", "MVP development", "open source"],
68+
},
69+
speaking: {
70+
title: "Speaking & Talks | Arvind Singharpuria",
71+
description: "Conference talks and presentations on DevOps, cloud architecture, and software development. Book me for your next event or meetup.",
72+
keywords: ["conference talks", "tech speaker", "presentations", "DevOps talks", "developer advocate"],
73+
},
74+
tutorials: {
75+
title: "Tutorials & Blog | Arvind Singharpuria",
76+
description: "Technical tutorials and blog posts on DevOps, cloud computing, containerization, and modern software development practices.",
77+
keywords: ["tutorials", "blog", "DevOps tutorials", "cloud computing", "technical articles"],
78+
},
79+
mvp: {
80+
title: "MVP Development Services | Fast, Beautiful, High-Quality",
81+
description: "We build fast, beautiful, and high-quality MVPs. Turn your vision into reality with expert full stack development, modern UI/UX, and reliable deployment.",
82+
keywords: ["MVP development", "startup development", "product development", "SaaS development", "web app development"],
83+
},
84+
} as const;
85+
86+
// JSON-LD Schemas
87+
export function getPersonSchema(siteUrl: string) {
88+
return {
89+
"@context": "https://schema.org",
90+
"@type": "Person",
91+
name: SEO_CONFIG.author.name,
92+
url: siteUrl,
93+
email: SEO_CONFIG.author.email,
94+
jobTitle: "Full Stack Developer & DevOps Engineer",
95+
description: SEO_CONFIG.defaultDescription,
96+
sameAs: [
97+
`https://twitter.com/${SEO_CONFIG.author.twitter.replace("@", "")}`,
98+
`https://github.com/${SEO_CONFIG.author.github}`,
99+
`https://linkedin.com/in/${SEO_CONFIG.author.linkedin}`,
100+
],
101+
knowsAbout: [
102+
"Full Stack Development",
103+
"DevOps",
104+
"Cloud Architecture",
105+
"AI/ML",
106+
"React",
107+
"Node.js",
108+
"Docker",
109+
"Kubernetes",
110+
],
111+
};
112+
}
113+
114+
export function getWebsiteSchema(siteUrl: string) {
115+
return {
116+
"@context": "https://schema.org",
117+
"@type": "WebSite",
118+
name: SEO_CONFIG.siteName,
119+
url: siteUrl,
120+
description: SEO_CONFIG.defaultDescription,
121+
author: {
122+
"@type": "Person",
123+
name: SEO_CONFIG.author.name,
124+
},
125+
};
126+
}
127+
128+
export function getArticleSchema(options: {
129+
title: string;
130+
description: string;
131+
url: string;
132+
publishedAt: Date;
133+
modifiedAt?: Date;
134+
image?: string;
135+
siteUrl: string;
136+
}) {
137+
return {
138+
"@context": "https://schema.org",
139+
"@type": "Article",
140+
headline: options.title,
141+
description: options.description,
142+
url: options.url,
143+
datePublished: options.publishedAt.toISOString(),
144+
dateModified: (options.modifiedAt || options.publishedAt).toISOString(),
145+
image: options.image || `${options.siteUrl}/opengraph-image.jpg`,
146+
author: {
147+
"@type": "Person",
148+
name: SEO_CONFIG.author.name,
149+
url: options.siteUrl,
150+
},
151+
publisher: {
152+
"@type": "Person",
153+
name: SEO_CONFIG.author.name,
154+
},
155+
};
156+
}
157+
158+
export function getServiceSchema(siteUrl: string) {
159+
return {
160+
"@context": "https://schema.org",
161+
"@type": "ProfessionalService",
162+
name: "MVP Development Services by Arvind Singharpuria",
163+
description: "We build fast, beautiful, and high-quality MVPs. Expert full stack development with modern UI/UX and reliable deployment.",
164+
url: `${siteUrl}/mvp`,
165+
provider: {
166+
"@type": "Person",
167+
name: SEO_CONFIG.author.name,
168+
},
169+
areaServed: "Worldwide",
170+
hasOfferCatalog: {
171+
"@type": "OfferCatalog",
172+
name: "MVP Development Services",
173+
itemListElement: [
174+
{
175+
"@type": "Offer",
176+
itemOffered: {
177+
"@type": "Service",
178+
name: "Landing Page Development",
179+
description: "Responsive design with modern UI/UX, contact forms, SEO optimization, and deployment included.",
180+
},
181+
price: "1000",
182+
priceCurrency: "USD",
183+
},
184+
{
185+
"@type": "Offer",
186+
itemOffered: {
187+
"@type": "Service",
188+
name: "MVP Development",
189+
description: "3-5 core features, custom UI/UX design, payment integration, user authentication, and CI/CD deployment.",
190+
},
191+
price: "5000",
192+
priceCurrency: "USD",
193+
},
194+
],
195+
},
196+
};
197+
}
198+

src/pages/index.astro

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ import convertAsteriskToStrongTag from "@/utils/convertAsteriskToStrongTag";
1111
1212
import presentation from "@/data/presentation";
1313
import companies from "@/data/companies";
14+
import { PAGE_SEO } from "@/data/seo";
1415
---
1516

16-
<Layout>
17+
<Layout
18+
title={PAGE_SEO.home.title}
19+
description={PAGE_SEO.home.description}
20+
keywords={PAGE_SEO.home.keywords}
21+
>
1722
<main class="flex flex-col gap-20">
1823
<article
1924
class="flex flex-col gap-8 md:flex-row-reverse md:justify-end md:gap-12"

src/pages/mvp.astro

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ import projects from "@/data/projects";
88
import companies from "@/data/companies";
99
1010
import presentation from "@/data/presentation";
11+
import { PAGE_SEO, getServiceSchema } from "@/data/seo";
12+
import { SITE_URL } from "@/data/config";
1113
---
1214

13-
<Layout>
15+
<Layout
16+
title={PAGE_SEO.mvp.title}
17+
description={PAGE_SEO.mvp.description}
18+
keywords={PAGE_SEO.mvp.keywords}
19+
jsonLd={getServiceSchema(SITE_URL)}
20+
>
1421
<main class="flex flex-col gap-12">
1522
<article class="flex flex-col gap-8 py-12">
1623
<header class="flex flex-col gap-6 text-center max-w-4xl mx-auto">

src/pages/projects.astro

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import ProjectCard from "@/components/ProjectCard.astro";
66
import projects from "@/data/projects";
77
88
import presentation from "@/data/presentation";
9+
import { PAGE_SEO } from "@/data/seo";
910
---
1011

11-
<Layout>
12+
<Layout
13+
title={PAGE_SEO.projects.title}
14+
description={PAGE_SEO.projects.description}
15+
keywords={PAGE_SEO.projects.keywords}
16+
>
1217
<main class="flex flex-col gap-12">
1318
<article class="flex flex-col gap-8">
1419
<header>

0 commit comments

Comments
 (0)