From a23665ee9e1186e33db0e5e33c4eb73b9c899386 Mon Sep 17 00:00:00 2001 From: Poyraz Avsever Date: Mon, 30 Mar 2026 11:28:05 +0300 Subject: [PATCH] feat(seo): implement google analytics, sitemap, robots and json-ld structured data --- app/about/page.tsx | 16 +++++++- app/blog/[slug]/page.tsx | 17 ++++++++- app/layout.tsx | 4 +- app/robots.ts | 16 ++++++++ app/sitemap.ts | 30 +++++++++++++++ components/json-ld.tsx | 82 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts create mode 100644 components/json-ld.tsx diff --git a/app/about/page.tsx b/app/about/page.tsx index 128f340..52e8fd2 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,5 +1,19 @@ import { AboutContent } from "@/components/about-content"; +import { PersonJsonLd } from "@/components/json-ld"; export default function AboutPage() { - return ; + return ( + <> + + + + ); } diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index c2a1e95..4b48efb 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -1,7 +1,10 @@ import { notFound } from "next/navigation"; import { BlogDetailContent } from "@/components/blog-detail-content"; +import { ArticleJsonLd } from "@/components/json-ld"; import { getBlogDetailBySlug } from "@/data/blog-detail"; +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com"; + type BlogDetailPageProps = { params: Promise<{ slug: string }>; }; @@ -14,5 +17,17 @@ export default async function BlogDetailPage({ params }: BlogDetailPageProps) { notFound(); } - return ; + return ( + <> + + + + ); } diff --git a/app/layout.tsx b/app/layout.tsx index 8699f75..ce1c870 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,8 @@ -import type { Metadata } from "next"; +import type { Metadata } from "next"; import "./globals.css"; import { AppShell } from "@/components/app-shell"; +import { GoogleAnalytics } from "@/components/google-analytics"; export const metadata: Metadata = { metadataBase: new URL("https://poyrazavsever.com"), @@ -85,6 +86,7 @@ export default function RootLayout({ return ( + {children} diff --git a/app/robots.ts b/app/robots.ts new file mode 100644 index 0000000..6825975 --- /dev/null +++ b/app/robots.ts @@ -0,0 +1,16 @@ +import type { MetadataRoute } from "next"; + +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com"; + +export default function robots(): MetadataRoute.Robots { + return { + rules: [ + { + userAgent: "*", + allow: "/", + disallow: ["/api/", "/_next/"], + }, + ], + sitemap: `${SITE_URL}/sitemap.xml`, + }; +} diff --git a/app/sitemap.ts b/app/sitemap.ts new file mode 100644 index 0000000..150a874 --- /dev/null +++ b/app/sitemap.ts @@ -0,0 +1,30 @@ +import { listBlogDetails } from "@/data/blog-detail"; +import type { MetadataRoute } from "next"; + +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com"; + +export default async function sitemap(): Promise { + const posts = await listBlogDetails(); + + const staticRoutes: MetadataRoute.Sitemap = [ + { url: SITE_URL, lastModified: new Date(), changeFrequency: "weekly", priority: 1.0 }, + { url: `${SITE_URL}/about`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 }, + { url: `${SITE_URL}/about/references`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.5 }, + { url: `${SITE_URL}/about/volunteer-community`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.5 }, + { url: `${SITE_URL}/blog`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.9 }, + { url: `${SITE_URL}/projects`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.8 }, + { url: `${SITE_URL}/content`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.7 }, + { url: `${SITE_URL}/contact`, lastModified: new Date(), changeFrequency: "yearly", priority: 0.5 }, + { url: `${SITE_URL}/links`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.4 }, + { url: `${SITE_URL}/snippets`, lastModified: new Date(), changeFrequency: "weekly", priority: 0.7 }, + ]; + + const blogRoutes: MetadataRoute.Sitemap = posts.map((post) => ({ + url: `${SITE_URL}/blog/${post.slug}`, + lastModified: post.date ? new Date(post.date) : new Date(), + changeFrequency: "monthly", + priority: 0.7, + })); + + return [...staticRoutes, ...blogRoutes]; +} diff --git a/components/json-ld.tsx b/components/json-ld.tsx new file mode 100644 index 0000000..89bc244 --- /dev/null +++ b/components/json-ld.tsx @@ -0,0 +1,82 @@ +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com"; + +export type PersonJsonLdProps = { + name: string; + url?: string; + image?: string; + jobTitle?: string; + sameAs?: string[]; +}; + +export function PersonJsonLd({ + name, + url = SITE_URL, + image = `${SITE_URL}/logo/logo.jpeg`, + jobTitle = "Fullstack Developer", + sameAs = [], +}: PersonJsonLdProps) { + const data = { + "@context": "https://schema.org", + "@type": "Person", + name, + url, + image, + jobTitle, + sameAs, + }; + + return ( +