Improve SEO metadata and structured data
هذا الالتزام موجود في:
@@ -1,7 +1,8 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { HomePage } from "@/components/home-page";
|
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() {
|
export function generateStaticParams() {
|
||||||
return languages.map((lang) => ({ lang }));
|
return languages.map((lang) => ({ lang }));
|
||||||
@@ -12,20 +13,7 @@ export function generateMetadata({ params }: { params: { lang: string } }): Meta
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const language = params.lang as Language;
|
return getHomeMetadata(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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LocalizedHomePage({ params }: { params: { lang: string } }) {
|
export default function LocalizedHomePage({ params }: { params: { lang: string } }) {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import { ResumePageContent } from "@/components/resume-page-content";
|
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() {
|
export function generateStaticParams() {
|
||||||
return languages.map((lang) => ({ lang }));
|
return languages.map((lang) => ({ lang }));
|
||||||
@@ -12,20 +13,7 @@ export function generateMetadata({ params }: { params: { lang: string } }): Meta
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const language = params.lang as Language;
|
return getResumeMetadata(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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LocalizedResumePage({ params }: { params: { lang: string } }) {
|
export default function LocalizedResumePage({ params }: { params: { lang: string } }) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import "./portfolio.css";
|
import "./portfolio.css";
|
||||||
|
import { getDefaultSeoMetadata } from "@/data/seo";
|
||||||
|
import { siteUrl } from "@/data/site-config";
|
||||||
|
|
||||||
const themeInitScript = `
|
const themeInitScript = `
|
||||||
(() => {
|
(() => {
|
||||||
@@ -18,22 +20,8 @@ const themeInitScript = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Grace Butrus Salmoun | Architectural Engineer Specialized in Architectural Design",
|
metadataBase: new URL(siteUrl),
|
||||||
description:
|
...getDefaultSeoMetadata(),
|
||||||
"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.",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -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 {
|
export default function robots(): MetadataRoute.Robots {
|
||||||
return {
|
return {
|
||||||
@@ -6,6 +7,7 @@ export default function robots(): MetadataRoute.Robots {
|
|||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
allow: "/",
|
allow: "/",
|
||||||
},
|
},
|
||||||
sitemap: "https://grace-salamoun-architect.vercel.app/sitemap.xml",
|
host: siteUrl,
|
||||||
|
sitemap: absoluteUrl("/sitemap.xml"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
const baseUrl = "https://grace-salamoun-architect.vercel.app";
|
const lastModified = new Date();
|
||||||
const lastModified = new Date("2026-04-18T00:00:00.000Z");
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/en`,
|
url: absoluteUrl("/en"),
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 1,
|
priority: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/ar`,
|
url: absoluteUrl("/ar"),
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 1,
|
priority: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/en/resume`,
|
url: absoluteUrl("/en/resume"),
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/ar/resume`,
|
url: absoluteUrl("/ar/resume"),
|
||||||
lastModified,
|
lastModified,
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { JsonLd } from "@/components/json-ld";
|
||||||
import { ProjectCard } from "@/components/project-card";
|
import { ProjectCard } from "@/components/project-card";
|
||||||
import { SectionHeading } from "@/components/section-heading";
|
import { SectionHeading } from "@/components/section-heading";
|
||||||
import { SiteShell } from "@/components/site-shell";
|
import { SiteShell } from "@/components/site-shell";
|
||||||
@@ -11,12 +12,14 @@ import {
|
|||||||
sharedProfile,
|
sharedProfile,
|
||||||
type Language,
|
type Language,
|
||||||
} from "@/data/portfolio";
|
} from "@/data/portfolio";
|
||||||
|
import { getHomeStructuredData } from "@/data/seo";
|
||||||
|
|
||||||
export function HomePage({ language }: { language: Language }) {
|
export function HomePage({ language }: { language: Language }) {
|
||||||
const dir = getDirection(language);
|
const dir = getDirection(language);
|
||||||
const t = portfolioContent[language];
|
const t = portfolioContent[language];
|
||||||
const founderName = language === "ar" ? sharedProfile.founderNameAr : sharedProfile.founderNameEn;
|
const founderName = language === "ar" ? sharedProfile.founderNameAr : sharedProfile.founderNameEn;
|
||||||
const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address;
|
const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address;
|
||||||
|
const structuredData = getHomeStructuredData(language);
|
||||||
const heroImageAlt =
|
const heroImageAlt =
|
||||||
language === "ar"
|
language === "ar"
|
||||||
? "لوحة بورتفوليو لمجمع إعلامي ومبنى التلفزيون"
|
? "لوحة بورتفوليو لمجمع إعلامي ومبنى التلفزيون"
|
||||||
@@ -50,6 +53,7 @@ export function HomePage({ language }: { language: Language }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteShell language={language}>
|
<SiteShell language={language}>
|
||||||
|
<JsonLd data={structuredData} />
|
||||||
<div className="top-wash pointer-events-none absolute inset-x-0 top-0 h-[720px]" />
|
<div className="top-wash pointer-events-none absolute inset-x-0 top-0 h-[720px]" />
|
||||||
|
|
||||||
<main className="site-container flex flex-col gap-12 py-6 md:gap-14 md:py-8 lg:gap-16 lg:py-10">
|
<main className="site-container flex flex-col gap-12 py-6 md:gap-14 md:py-8 lg:gap-16 lg:py-10">
|
||||||
|
|||||||
7
components/json-ld.tsx
Normal file
7
components/json-ld.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
type JsonLdProps = {
|
||||||
|
data: Record<string, unknown> | Array<Record<string, unknown>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function JsonLd({ data }: JsonLdProps) {
|
||||||
|
return <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />;
|
||||||
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { JsonLd } from "@/components/json-ld";
|
||||||
import { SectionHeading } from "@/components/section-heading";
|
import { SectionHeading } from "@/components/section-heading";
|
||||||
import { SiteShell } from "@/components/site-shell";
|
import { SiteShell } from "@/components/site-shell";
|
||||||
import { portfolioContent, resumeFile, sharedProfile, type Language } from "@/data/portfolio";
|
import { portfolioContent, resumeFile, sharedProfile, type Language } from "@/data/portfolio";
|
||||||
|
import { getResumeStructuredData } from "@/data/seo";
|
||||||
|
|
||||||
export function ResumePageContent({ language }: { language: Language }) {
|
export function ResumePageContent({ language }: { language: Language }) {
|
||||||
const t = portfolioContent[language];
|
const t = portfolioContent[language];
|
||||||
const brandName = language === "ar" ? sharedProfile.brandNameAr : sharedProfile.brandNameEn;
|
const brandName = language === "ar" ? sharedProfile.brandNameAr : sharedProfile.brandNameEn;
|
||||||
const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address;
|
const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address;
|
||||||
const hasResume = resumeFile.available;
|
const hasResume = resumeFile.available;
|
||||||
|
const structuredData = getResumeStructuredData(language);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SiteShell language={language}>
|
<SiteShell language={language}>
|
||||||
|
<JsonLd data={structuredData} />
|
||||||
<main className="site-container flex flex-col gap-10 py-8 md:py-10 lg:gap-12 lg:py-12">
|
<main className="site-container flex flex-col gap-10 py-8 md:py-10 lg:gap-12 lg:py-12">
|
||||||
<section className="section-shell hero-glow">
|
<section className="section-shell hero-glow">
|
||||||
<div className="section-padding grid gap-8 lg:grid-cols-[1fr_auto] lg:items-end">
|
<div className="section-padding grid gap-8 lg:grid-cols-[1fr_auto] lg:items-end">
|
||||||
|
|||||||
256
data/seo.ts
Normal file
256
data/seo.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { englishPortfolioContent, getPortfolioContent, sharedProfile, type Language } from "@/data/portfolio";
|
||||||
|
import { absoluteUrl, siteName } from "@/data/site-config";
|
||||||
|
|
||||||
|
const sharedKeywords = [
|
||||||
|
"architecture portfolio",
|
||||||
|
"architectural engineer",
|
||||||
|
"architectural design",
|
||||||
|
"urban rehabilitation",
|
||||||
|
"landscape design",
|
||||||
|
"shop drawings",
|
||||||
|
"architectural visualization",
|
||||||
|
"Damascus architect",
|
||||||
|
];
|
||||||
|
|
||||||
|
const localizedKeywords: Record<Language, string[]> = {
|
||||||
|
en: ["portfolio architect", "presentation boards", "mixed-use towers", "hospitality design"],
|
||||||
|
ar: ["بورتفوليو معماري", "مهندسة معمارية", "تصميم معماري", "إعادة تأهيل عمراني"],
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLocale(language: Language) {
|
||||||
|
return language === "ar" ? "ar_SY" : "en_US";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlternateLocales(language: Language) {
|
||||||
|
return language === "ar" ? ["en_US"] : ["ar_SY"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOgImage(language: Language) {
|
||||||
|
return {
|
||||||
|
url: absoluteUrl(sharedProfile.heroImage),
|
||||||
|
alt:
|
||||||
|
language === "ar"
|
||||||
|
? "معاينة بورتفوليو غريس بطرس سلمون"
|
||||||
|
: "Portfolio preview for Grace Butrus Salmoun",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMetadata({
|
||||||
|
language,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
canonicalPath,
|
||||||
|
pageType,
|
||||||
|
}: {
|
||||||
|
language: Language;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
canonicalPath: string;
|
||||||
|
pageType: "website" | "profile";
|
||||||
|
}): Metadata {
|
||||||
|
const ogImage = getOgImage(language);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
keywords: [...sharedKeywords, ...localizedKeywords[language]],
|
||||||
|
authors: [{ name: sharedProfile.founderNameEn }],
|
||||||
|
creator: sharedProfile.founderNameEn,
|
||||||
|
publisher: sharedProfile.founderNameEn,
|
||||||
|
category: "architecture portfolio",
|
||||||
|
alternates: {
|
||||||
|
canonical: canonicalPath,
|
||||||
|
languages: {
|
||||||
|
en: canonicalPath.includes("/resume") ? "/en/resume" : "/en",
|
||||||
|
ar: canonicalPath.includes("/resume") ? "/ar/resume" : "/ar",
|
||||||
|
"x-default": "/en",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
"max-image-preview": "large",
|
||||||
|
"max-snippet": -1,
|
||||||
|
"max-video-preview": -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
url: absoluteUrl(canonicalPath),
|
||||||
|
siteName,
|
||||||
|
locale: getLocale(language),
|
||||||
|
alternateLocale: getAlternateLocales(language),
|
||||||
|
type: pageType,
|
||||||
|
images: [ogImage],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
images: [ogImage.url],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultSeoMetadata(): Metadata {
|
||||||
|
const title = englishPortfolioContent.meta.title;
|
||||||
|
const description = englishPortfolioContent.meta.description;
|
||||||
|
const ogImage = getOgImage("en");
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
applicationName: siteName,
|
||||||
|
authors: [{ name: sharedProfile.founderNameEn }],
|
||||||
|
creator: sharedProfile.founderNameEn,
|
||||||
|
publisher: sharedProfile.founderNameEn,
|
||||||
|
keywords: [...sharedKeywords, ...localizedKeywords.en],
|
||||||
|
category: "architecture portfolio",
|
||||||
|
referrer: "origin-when-cross-origin",
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
"max-image-preview": "large",
|
||||||
|
"max-snippet": -1,
|
||||||
|
"max-video-preview": -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
url: absoluteUrl("/en"),
|
||||||
|
siteName,
|
||||||
|
locale: "en_US",
|
||||||
|
alternateLocale: ["ar_SY"],
|
||||||
|
type: "website",
|
||||||
|
images: [ogImage],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
images: [ogImage.url],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHomeMetadata(language: Language): Metadata {
|
||||||
|
const t = getPortfolioContent(language);
|
||||||
|
|
||||||
|
return buildMetadata({
|
||||||
|
language,
|
||||||
|
title: t.meta.title,
|
||||||
|
description: t.meta.description,
|
||||||
|
canonicalPath: `/${language}`,
|
||||||
|
pageType: "website",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResumeMetadata(language: Language): Metadata {
|
||||||
|
const t = getPortfolioContent(language);
|
||||||
|
const title = language === "ar" ? `السيرة الذاتية | ${t.meta.title}` : `Resume | ${t.meta.title}`;
|
||||||
|
|
||||||
|
return buildMetadata({
|
||||||
|
language,
|
||||||
|
title,
|
||||||
|
description: t.resume.description,
|
||||||
|
canonicalPath: `/${language}/resume`,
|
||||||
|
pageType: "profile",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHomeStructuredData(language: Language) {
|
||||||
|
const t = getPortfolioContent(language);
|
||||||
|
const name = language === "ar" ? sharedProfile.founderNameAr : sharedProfile.founderNameEn;
|
||||||
|
const address = language === "ar" ? sharedProfile.addressAr : sharedProfile.address;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebSite",
|
||||||
|
name: siteName,
|
||||||
|
url: absoluteUrl("/"),
|
||||||
|
inLanguage: ["en", "ar"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Person",
|
||||||
|
name,
|
||||||
|
url: absoluteUrl(`/${language}`),
|
||||||
|
image: absoluteUrl(sharedProfile.heroImage),
|
||||||
|
jobTitle: t.ui.architectureEngineer,
|
||||||
|
description: t.meta.description,
|
||||||
|
email: sharedProfile.email,
|
||||||
|
telephone: sharedProfile.phone,
|
||||||
|
address: {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
addressLocality: address,
|
||||||
|
addressCountry: "SY",
|
||||||
|
},
|
||||||
|
sameAs: [sharedProfile.facebookHref],
|
||||||
|
knowsAbout: [...t.craft.skills.slice(0, 4), ...t.sectors.categories.slice(0, 4)],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "CollectionPage",
|
||||||
|
name: t.meta.title,
|
||||||
|
description: t.meta.description,
|
||||||
|
url: absoluteUrl(`/${language}`),
|
||||||
|
inLanguage: language,
|
||||||
|
isPartOf: {
|
||||||
|
"@type": "WebSite",
|
||||||
|
name: siteName,
|
||||||
|
url: absoluteUrl("/"),
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
"@type": "Person",
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResumeStructuredData(language: Language) {
|
||||||
|
const t = getPortfolioContent(language);
|
||||||
|
const name = language === "ar" ? sharedProfile.founderNameAr : sharedProfile.founderNameEn;
|
||||||
|
const title = language === "ar" ? `السيرة الذاتية | ${name}` : `Resume | ${name}`;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Person",
|
||||||
|
name,
|
||||||
|
url: absoluteUrl(`/${language}`),
|
||||||
|
email: sharedProfile.email,
|
||||||
|
telephone: sharedProfile.phone,
|
||||||
|
image: absoluteUrl(sharedProfile.heroImage),
|
||||||
|
jobTitle: t.ui.architectureEngineer,
|
||||||
|
sameAs: [sharedProfile.facebookHref],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ProfilePage",
|
||||||
|
name: title,
|
||||||
|
description: t.resume.description,
|
||||||
|
url: absoluteUrl(`/${language}/resume`),
|
||||||
|
inLanguage: language,
|
||||||
|
isPartOf: {
|
||||||
|
"@type": "WebSite",
|
||||||
|
name: siteName,
|
||||||
|
url: absoluteUrl("/"),
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
"@type": "Person",
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
8
data/site-config.ts
Normal file
8
data/site-config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const FALLBACK_SITE_URL = "https://grace-salamoun-architect.vercel.app";
|
||||||
|
|
||||||
|
export const siteName = "Grace Butrus Salmoun Portfolio";
|
||||||
|
export const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL ?? FALLBACK_SITE_URL).replace(/\/+$/, "");
|
||||||
|
|
||||||
|
export function absoluteUrl(path = "/") {
|
||||||
|
return new URL(path, `${siteUrl}/`).toString();
|
||||||
|
}
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم