From 7ec99734c53148825cb30af1fff941d215e82309 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Wed, 6 May 2026 18:06:12 -0500 Subject: [PATCH] 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 --- src/app/[locale]/layout.tsx | 6 +++- src/app/[locale]/news/page.tsx | 17 ++++++++- src/lib/seo.ts | 64 ++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 2914a7e..89e87ac 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -20,7 +20,7 @@ export function generateStaticParams() { return routing.locales.map((locale) => ({ locale })); } 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'; const inter = Inter({ subsets: ["latin"] }); @@ -154,6 +154,10 @@ export default async function RootLayout({ logoUrl: branding.logoUrl, sameAs: sameAs.length ? sameAs : undefined, }), + localBusinessSchema({ + logoUrl: branding.logoUrl, + sameAs: sameAs.length ? sameAs : undefined, + }), websiteSchema(), ]} /> diff --git a/src/app/[locale]/news/page.tsx b/src/app/[locale]/news/page.tsx index f1e9ece..0e49c04 100644 --- a/src/app/[locale]/news/page.tsx +++ b/src/app/[locale]/news/page.tsx @@ -7,7 +7,8 @@ import BreathingField from "@/components/visuals/BreathingField"; import { getLocalizedData } from "@/lib/i18nHelper"; 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. 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 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 (
+ {collectionSchema && }
diff --git a/src/lib/seo.ts b/src/lib/seo.ts index d2ac018..83e1322 100644 --- a/src/lib/seo.ts +++ b/src/lib/seo.ts @@ -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() { const base = baseUrl(); return {