
As a Full Stack Developer, I recently rebuilt my portfolio website using Next.js 15 and wanted to ensure it ranks well on Google. In this article, I'll share the complete SEO strategy I implemented,including metadata management, structured data, dynamic OG images, and multilingual support
The Problem
When I searched for my name on Google, my portfolio was nowhere to be found. Instead, other websites with similar names appeared first. I needed a comprehensive SEO strategy to fix this.
## The Solution: A Multi-Layer SEO Approach
### 1. Centralized SEO Configuration
First, I created a centralized configuration file to manage all SEO-related data:
typescript
// src/lib/seo/config.ts
export const BASE_URL = "https://www.eduzen.uz";
export const seoConfig = {
siteName: "Matkarim Matkarimov",
author: {
name: "Matkarim Matkarimov",
alternateName: "Matkarimov Matkarim",
jobTitle: "Full Stack Developer",
email: "matkarimov1099@gmail.com",
},
social: {
github: "https://github.com/matkarimov099",
linkedin: "https://www.linkedin.com/in/matkarim-matkarimov/",
telegram: "https://t.me/m_matkarimov",
},
keywords: [
"Matkarimov Matkarim",
"Full Stack Developer",
"React Developer",
"Next.js Developer",
// ... more keywords
],
} as const;
2. Reusable Metadata Generator
Instead of writing metadata manually for each page, I created a reusable function:
// src/lib/seo/metadata.ts
import type { Metadata } from "next";
interface GenerateMetadataOptions {
title: string;
description: string;
path: string;
locale: Locale;
type?: "website" | "article" | "profile";
}
export function generatePageMetadata({
title,
description,
path,
locale,
type = "website",
}: GenerateMetadataOptions): Metadata {
const url = `${BASE_URL}/${locale}${path}`;
// Generate hreflang alternates for all locales
const languages: Record<string, string> = {};
for (const loc of locales) {
languages[loc] = `${BASE_URL}/${loc}${path}`;
}
languages["x-default"] = `${BASE_URL}/${defaultLocale}${path}`;
return {
title,
description,
alternates: {
canonical: url,
languages,
},
openGraph: {
title,
description,
url,
siteName: seoConfig.siteName,
locale: ogLocaleMap[locale],
type,
},
twitter: {
card: "summary_large_image",
title,
description,
},
robots: {
index: true,
follow: true,
},
};
}
Now each page only needs a few lines:
// src/app/[locale]/about/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const data = seoData[locale as Locale];
return generatePageMetadata({
title: data.title,
description: data.description,
path: "/about",
locale: locale as Locale,
type: "profile",
});
}
3. Dynamic OG Images with next/og
Static OG images are boring. I created dynamic images that are generated on-the-fly:
// src/app/opengraph-image.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const alt = "Matkarimov Matkarim - Full Stack Developer";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
export default async function Image() {
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#09090b",
}}
>
<img
src="https://avatars.githubusercontent.com/matkarimov099"
alt="Avatar"
width={150}
height={150}
style={{ borderRadius: "50%" }}
/>
<h1 style={{ color: "white", fontSize: 64 }}>
Matkarimov Matkarim
</h1>
<p style={{ color: "#10b981", fontSize: 32 }}>
Full Stack Developer
</p>
</div>
),
{ ...size }
);
}
4. Schema.org Structured Data
Structured data helps Google understand your content better:
// src/lib/seo/structured-data.ts
export function generatePersonSchema() {
return {
"@context": "https://schema.org",
"@type": "Person",
"@id": `${BASE_URL}/#person`,
name: "Matkarim Matkarimov",
alternateName: "Matkarimov Matkarim",
jobTitle: "Full Stack Developer",
url: BASE_URL,
sameAs: [
"https://github.com/matkarimov099",
"https://linkedin.com/in/matkarim-matkarimov",
],
knowsAbout: [
"React", "Next.js", "TypeScript", "Node.js", "PostgreSQL"
],
worksFor: {
"@type": "Organization",
name: "UNICON-SOFT",
},
};
}
Inject it into your layout:
// src/app/[locale]/layout.tsx
import { JsonLd } from "@/components/seo";
import { generatePersonSchema, generateWebSiteSchema } from "@/lib/seo";
export default function LocaleLayout({ children }) {
return (
<>
<JsonLd data={generatePersonSchema()} />
<JsonLd data={generateWebSiteSchema()} />
{children}
</>
);
}
5. Sitemap and Robots.txt
Next.js makes this easy with special files:
// src/app/sitemap.ts
import type { MetadataRoute } from "next";
export default function sitemap(): MetadataRoute.Sitemap {
const pages = ["", "/about", "/projects", "/contact"];
const locales = ["en", "ru", "uz"];
return pages.flatMap((page) =>
locales.map((locale) => ({
url: `${BASE_URL}/${locale}${page}`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: page === "" ? 1.0 : 0.8,
}))
);
}
// src/app/robots.ts
import type { MetadataRoute } from "next";
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/api/", "/_next/"],
},
],
sitemap: `${BASE_URL}/sitemap.xml`,
};
}
## Results
After implementing these changes:
1. ✅ All pages are properly indexed
2. ✅ Rich snippets appear in search results
3. ✅ OG images display correctly on social media
4. ✅ Multilingual support with proper hreflang tags
Key Takeaways
1. Centralize your SEO config - Makes maintenance easier
2. Use dynamic OG images - They're more engaging and always up-to-date
3. Implement structured data - Helps Google understand your content
4. Don't forget hreflang - Essential for multilingual sites
5. Submit to Google Search Console - Speed up indexing
Check It Out
You can see all of this in action on my portfolio: https://www.eduzen.uz
Feel free to connect with me:
- GitHub: https://github.com/matkarimov099
- LinkedIn: https://www.linkedin.com/in/matkarim-matkarimov/
- Telegram: https://t.me/m_matkarimov
---
What SEO strategies do you use for your portfolio? Let me know in the comments!
---
Top comments (0)