From bd1bb5c2a89cc17cd4be43e12a7b1ba41f03b868 Mon Sep 17 00:00:00 2001 From: boutmoun123 Date: Sun, 3 May 2026 15:54:35 +0300 Subject: [PATCH] Improve SEO metadata and structured data --- app/[lang]/page.tsx | 18 +- app/[lang]/resume/page.tsx | 18 +- app/layout.tsx | 20 +-- app/robots.ts | 6 +- app/sitemap.ts | 14 +- components/home-page.tsx | 4 + components/json-ld.tsx | 7 + components/resume-page-content.tsx | 4 + data/seo.ts | 256 +++++++++++++++++++++++++++++ data/site-config.ts | 8 + 10 files changed, 300 insertions(+), 55 deletions(-) create mode 100644 components/json-ld.tsx create mode 100644 data/seo.ts create mode 100644 data/site-config.ts diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx index a7e3eb2..57a96a4 100644 --- a/app/[lang]/page.tsx +++ b/app/[lang]/page.tsx @@ -1,7 +1,8 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { HomePage } from "@/components/home-page"; -import { getPortfolioContent, isLanguage, languages, type Language } from "@/data/portfolio"; +import { isLanguage, languages, type Language } from "@/data/portfolio"; +import { getHomeMetadata } from "@/data/seo"; export function generateStaticParams() { return languages.map((lang) => ({ lang })); @@ -12,20 +13,7 @@ export function generateMetadata({ params }: { params: { lang: string } }): Meta return {}; } - const language = params.lang as Language; - const t = getPortfolioContent(language); - - return { - title: t.meta.title, - description: t.meta.description, - alternates: { - canonical: `/${language}`, - languages: { - en: "/en", - ar: "/ar", - }, - }, - }; + return getHomeMetadata(params.lang as Language); } export default function LocalizedHomePage({ params }: { params: { lang: string } }) { diff --git a/app/[lang]/resume/page.tsx b/app/[lang]/resume/page.tsx index b7c6e8f..69d8ab1 100644 --- a/app/[lang]/resume/page.tsx +++ b/app/[lang]/resume/page.tsx @@ -1,7 +1,8 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { ResumePageContent } from "@/components/resume-page-content"; -import { getPortfolioContent, isLanguage, languages, type Language } from "@/data/portfolio"; +import { isLanguage, languages, type Language } from "@/data/portfolio"; +import { getResumeMetadata } from "@/data/seo"; export function generateStaticParams() { return languages.map((lang) => ({ lang })); @@ -12,20 +13,7 @@ export function generateMetadata({ params }: { params: { lang: string } }): Meta return {}; } - const language = params.lang as Language; - const t = getPortfolioContent(language); - - return { - title: language === "ar" ? `السيرة الذاتية | ${t.meta.title}` : `Resume | ${t.meta.title}`, - description: t.resume.description, - alternates: { - canonical: `/${language}/resume`, - languages: { - en: "/en/resume", - ar: "/ar/resume", - }, - }, - }; + return getResumeMetadata(params.lang as Language); } export default function LocalizedResumePage({ params }: { params: { lang: string } }) { diff --git a/app/layout.tsx b/app/layout.tsx index 315794a..ed4b8ec 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import "./globals.css"; import "./portfolio.css"; +import { getDefaultSeoMetadata } from "@/data/seo"; +import { siteUrl } from "@/data/site-config"; const themeInitScript = ` (() => { @@ -18,22 +20,8 @@ const themeInitScript = ` `; export const metadata: Metadata = { - title: "Grace Butrus Salmoun | Architectural Engineer Specialized in Architectural Design", - description: - "A bilingual architecture portfolio for Grace Butrus Salmoun, an architectural engineer specialized in architectural design, working across urban rehabilitation, landscape, shop drawings, and visual presentation.", - metadataBase: new URL("https://grace-salamoun-architect.vercel.app"), - openGraph: { - title: "Grace Butrus Salmoun | Architectural Engineer Specialized in Architectural Design", - description: - "Architectural design, urban rehabilitation, landscape, shop drawings, and visual presentation in English and Arabic.", - type: "website", - }, - twitter: { - card: "summary_large_image", - title: "Grace Butrus Salmoun | Architectural Engineer Specialized in Architectural Design", - description: - "A bilingual architecture portfolio featuring selected works, technical documentation, and visual presentation.", - }, + metadataBase: new URL(siteUrl), + ...getDefaultSeoMetadata(), }; export default function RootLayout({ diff --git a/app/robots.ts b/app/robots.ts index 93d4d15..5a75e23 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -1,4 +1,5 @@ -import type { MetadataRoute } from "next"; +import type { MetadataRoute } from "next"; +import { absoluteUrl, siteUrl } from "@/data/site-config"; export default function robots(): MetadataRoute.Robots { return { @@ -6,6 +7,7 @@ export default function robots(): MetadataRoute.Robots { userAgent: "*", allow: "/", }, - sitemap: "https://grace-salamoun-architect.vercel.app/sitemap.xml", + host: siteUrl, + sitemap: absoluteUrl("/sitemap.xml"), }; } diff --git a/app/sitemap.ts b/app/sitemap.ts index 103301f..a6531c3 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,30 +1,30 @@ -import type { MetadataRoute } from "next"; +import type { MetadataRoute } from "next"; +import { absoluteUrl } from "@/data/site-config"; export default function sitemap(): MetadataRoute.Sitemap { - const baseUrl = "https://grace-salamoun-architect.vercel.app"; - const lastModified = new Date("2026-04-18T00:00:00.000Z"); + const lastModified = new Date(); return [ { - url: `${baseUrl}/en`, + url: absoluteUrl("/en"), lastModified, changeFrequency: "monthly", priority: 1, }, { - url: `${baseUrl}/ar`, + url: absoluteUrl("/ar"), lastModified, changeFrequency: "monthly", priority: 1, }, { - url: `${baseUrl}/en/resume`, + url: absoluteUrl("/en/resume"), lastModified, changeFrequency: "monthly", priority: 0.8, }, { - url: `${baseUrl}/ar/resume`, + url: absoluteUrl("/ar/resume"), lastModified, changeFrequency: "monthly", priority: 0.8, diff --git a/components/home-page.tsx b/components/home-page.tsx index b7c4be4..ff41700 100644 --- a/components/home-page.tsx +++ b/components/home-page.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import Link from "next/link"; +import { JsonLd } from "@/components/json-ld"; import { ProjectCard } from "@/components/project-card"; import { SectionHeading } from "@/components/section-heading"; import { SiteShell } from "@/components/site-shell"; @@ -11,12 +12,14 @@ import { sharedProfile, type Language, } from "@/data/portfolio"; +import { getHomeStructuredData } from "@/data/seo"; export function HomePage({ language }: { language: Language }) { const dir = getDirection(language); const t = portfolioContent[language]; const founderName = language === "ar" ? sharedProfile.founderNameAr : sharedProfile.founderNameEn; const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address; + const structuredData = getHomeStructuredData(language); const heroImageAlt = language === "ar" ? "لوحة بورتفوليو لمجمع إعلامي ومبنى التلفزيون" @@ -50,6 +53,7 @@ export function HomePage({ language }: { language: Language }) { return ( +
diff --git a/components/json-ld.tsx b/components/json-ld.tsx new file mode 100644 index 0000000..656cec4 --- /dev/null +++ b/components/json-ld.tsx @@ -0,0 +1,7 @@ +type JsonLdProps = { + data: Record | Array>; +}; + +export function JsonLd({ data }: JsonLdProps) { + return