seo: project pages — canonical URLs, keywords, Twitter card, Service JSON-LD structured data

This commit is contained in:
Zac Gaetano 2026-05-01 11:14:54 -04:00
parent b822dbee40
commit 575dc752df

View file

@ -20,12 +20,42 @@ export async function generateMetadata({
if (!project) return { title: "Project Not Found" }; if (!project) return { title: "Project Not Found" };
return { return {
title: `${project.client} | Zachary Gaetano`, title: `${project.client} | Zachary Gaetano — Broadcast Systems Integration`,
description: project.summary, description: project.summary,
keywords: [
...project.technologies,
project.category,
project.client,
"broadcast systems integration",
"Zachary Gaetano",
"Wild Dragon",
"Washington DC",
"broadcast facility design",
],
alternates: {
canonical: `https://www.wilddragon.net/projects/${slug}`,
},
openGraph: { openGraph: {
title: `${project.client} | Zachary Gaetano`, title: `${project.client} | Zachary Gaetano`,
description: project.summary, description: project.summary,
images: [{ url: project.thumbnail }], url: `https://www.wilddragon.net/projects/${slug}`,
siteName: "Wild Dragon",
type: "website",
locale: "en_US",
images: [
{
url: project.thumbnail,
width: 1200,
height: 630,
alt: `${project.client}${project.category}`,
},
],
},
twitter: {
card: "summary_large_image",
title: `${project.client} | Zachary Gaetano`,
description: project.summary,
images: [project.thumbnail],
}, },
}; };
} }
@ -42,6 +72,18 @@ export default async function ProjectPage({
notFound(); notFound();
} }
const structuredData = {
"@context": "https://schema.org",
"@type": "Service",
name: project.title,
description: project.summary,
serviceType: project.category,
provider: {
"@id": "https://www.wilddragon.net/#organization",
},
areaServed: "United States",
};
// Find previous and next projects for navigation // Find previous and next projects for navigation
const currentIndex = projects.findIndex((p) => p.slug === project.slug); const currentIndex = projects.findIndex((p) => p.slug === project.slug);
const prevProject = currentIndex > 0 ? projects[currentIndex - 1] : null; const prevProject = currentIndex > 0 ? projects[currentIndex - 1] : null;
@ -50,6 +92,11 @@ export default async function ProjectPage({
return ( return (
<main className="min-h-screen bg-white"> <main className="min-h-screen bg-white">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
{/* Header bar */} {/* Header bar */}
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-lg shadow-sm py-3.5"> <nav className="fixed top-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-lg shadow-sm py-3.5">
<div className="max-w-6xl mx-auto px-6 flex items-center justify-between"> <div className="max-w-6xl mx-auto px-6 flex items-center justify-between">
@ -95,7 +142,7 @@ export default async function ProjectPage({
<div className="relative h-64 md:h-[28rem] overflow-hidden"> <div className="relative h-64 md:h-[28rem] overflow-hidden">
<Image <Image
src={project.thumbnail} src={project.thumbnail}
alt={project.title} alt={`${project.client}${project.category} broadcast facility`}
fill fill
className="object-cover" className="object-cover"
sizes="100vw" sizes="100vw"