feat(seo): LocalBusiness + CollectionPage structured data schemas
- Add localBusinessSchema() with geo coords, phone, opening hours for Google Local Pack and Knowledge Panel visibility - Add collectionPageSchema() with ItemList for article listing pages - Inject LocalBusiness alongside Organization+WebSite in root layout - Inject CollectionPage in /news hub page with article items Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ export function generateStaticParams() {
|
|||||||
return routing.locales.map((locale) => ({ locale }));
|
return routing.locales.map((locale) => ({ locale }));
|
||||||
}
|
}
|
||||||
import { getBranding, getSocialLinks } from '@/lib/siteSettings';
|
import { getBranding, getSocialLinks } from '@/lib/siteSettings';
|
||||||
import { organizationSchema, websiteSchema } from '@/lib/seo';
|
import { organizationSchema, websiteSchema, localBusinessSchema } from '@/lib/seo';
|
||||||
import JsonLd from '@/components/seo/JsonLd';
|
import JsonLd from '@/components/seo/JsonLd';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
@@ -154,6 +154,10 @@ export default async function RootLayout({
|
|||||||
logoUrl: branding.logoUrl,
|
logoUrl: branding.logoUrl,
|
||||||
sameAs: sameAs.length ? sameAs : undefined,
|
sameAs: sameAs.length ? sameAs : undefined,
|
||||||
}),
|
}),
|
||||||
|
localBusinessSchema({
|
||||||
|
logoUrl: branding.logoUrl,
|
||||||
|
sameAs: sameAs.length ? sameAs : undefined,
|
||||||
|
}),
|
||||||
websiteSchema(),
|
websiteSchema(),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import BreathingField from "@/components/visuals/BreathingField";
|
|||||||
|
|
||||||
import { getLocalizedData } from "@/lib/i18nHelper";
|
import { getLocalizedData } from "@/lib/i18nHelper";
|
||||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||||
import { buildPageMetadata } from "@/lib/seo";
|
import { buildPageMetadata, collectionPageSchema, baseUrl } from "@/lib/seo";
|
||||||
|
import JsonLd from "@/components/seo/JsonLd";
|
||||||
|
|
||||||
// ISR: revalidate every 60s — see /[locale]/page.tsx for the full rationale.
|
// ISR: revalidate every 60s — see /[locale]/page.tsx for the full rationale.
|
||||||
export const revalidate = 60;
|
export const revalidate = 60;
|
||||||
@@ -49,8 +50,22 @@ export default async function NewsHub({ params }: { params: Promise<{ locale: st
|
|||||||
const heroArticle = articles.length > 0 ? articles[0] : null;
|
const heroArticle = articles.length > 0 ? articles[0] : null;
|
||||||
const gridArticles = articles.length > 1 ? articles.slice(1) : [];
|
const gridArticles = articles.length > 1 ? articles.slice(1) : [];
|
||||||
|
|
||||||
|
const collectionSchema = articles.length > 0
|
||||||
|
? collectionPageSchema({
|
||||||
|
name: `${t("title1")} ${t("title2")} — FLUX`,
|
||||||
|
description: t("description"),
|
||||||
|
url: `${baseUrl()}/${locale}/news`,
|
||||||
|
items: articles.map((a: any, idx: number) => ({
|
||||||
|
name: a.title,
|
||||||
|
url: `${baseUrl()}/${locale}/news/${a.slug}`,
|
||||||
|
position: idx + 1,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen pt-32 pb-24">
|
<main className="relative min-h-screen pt-32 pb-24">
|
||||||
|
{collectionSchema && <JsonLd data={collectionSchema} />}
|
||||||
<BreathingField />
|
<BreathingField />
|
||||||
|
|
||||||
<div className="relative z-10 max-w-7xl mx-auto px-6">
|
<div className="relative z-10 max-w-7xl mx-auto px-6">
|
||||||
|
|||||||
@@ -114,6 +114,70 @@ export function organizationSchema(opts?: { logoUrl?: string; sameAs?: string[]
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function localBusinessSchema(opts?: { logoUrl?: string; sameAs?: string[] }) {
|
||||||
|
const base = baseUrl();
|
||||||
|
return {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "LocalBusiness",
|
||||||
|
"@id": `${base}/#local-business`,
|
||||||
|
name: "FLUX Srl",
|
||||||
|
legalName: "FLUX Srl",
|
||||||
|
url: base,
|
||||||
|
logo: opts?.logoUrl ? absoluteUrl(opts.logoUrl) : `${base}/flux-logo.png`,
|
||||||
|
description:
|
||||||
|
"Manufacturer of solid-state Radio Frequency (RF), Microwave, and Infrared industrial equipment since 1978.",
|
||||||
|
foundingDate: "1978",
|
||||||
|
founder: { "@type": "Person", name: "Patrizio Grando" },
|
||||||
|
address: {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
streetAddress: "Via Benedetto Marcello 32",
|
||||||
|
addressLocality: "Romano d'Ezzelino",
|
||||||
|
addressRegion: "Vicenza",
|
||||||
|
postalCode: "36060",
|
||||||
|
addressCountry: "IT",
|
||||||
|
},
|
||||||
|
geo: {
|
||||||
|
"@type": "GeoCoordinates",
|
||||||
|
latitude: 45.7836,
|
||||||
|
longitude: 11.7677,
|
||||||
|
},
|
||||||
|
telephone: "+39 0424 287 492",
|
||||||
|
email: "info@rf-flux.com",
|
||||||
|
openingHoursSpecification: {
|
||||||
|
"@type": "OpeningHoursSpecification",
|
||||||
|
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
|
||||||
|
opens: "08:00",
|
||||||
|
closes: "17:00",
|
||||||
|
},
|
||||||
|
areaServed: { "@type": "Place", name: "Worldwide" },
|
||||||
|
...(opts?.sameAs ? { sameAs: opts.sameAs } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectionPageSchema(opts: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
items: { name: string; url: string; position: number }[];
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "CollectionPage",
|
||||||
|
name: opts.name,
|
||||||
|
description: opts.description,
|
||||||
|
url: opts.url,
|
||||||
|
mainEntity: {
|
||||||
|
"@type": "ItemList",
|
||||||
|
itemListElement: opts.items.map((item) => ({
|
||||||
|
"@type": "ListItem",
|
||||||
|
position: item.position,
|
||||||
|
url: item.url,
|
||||||
|
name: item.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function websiteSchema() {
|
export function websiteSchema() {
|
||||||
const base = baseUrl();
|
const base = baseUrl();
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user