From 09e6d0c7cfec97ccef65ea41a6cedc90ab2132fa Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Mon, 4 May 2026 14:42:43 -0500 Subject: [PATCH] seo: dynamic sitemap + robots + per-page metadata + JSON-LD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the site up to enterprise SEO standards. Google now gets a complete machine-readable map of the content, with multilingual hreflang tags, structured data for the knowledge panel, and rich Open Graph cards on LinkedIn / WhatsApp / Twitter. NEW - src/app/sitemap.ts: dynamic sitemap.xml from Prisma. Emits 5 locales x every active application + every active news article, with hreflang alternates linking each translation. Hourly revalidation. - src/app/robots.ts: robots.txt blocks /hq-command/, /api/, /parts (B2B auth-gated), points crawlers at the sitemap. - src/lib/seo.ts: helpers for canonical URLs, hreflang alternates, and JSON-LD schemas (Organization, WebSite, Article, Product, BreadcrumbList). - src/components/seo/JsonLd.tsx: server component that emits one application/ld+json script tag per page. PER-PAGE generateMetadata - Home: localized titles + descriptions in EN/IT/VEC/ES/DE - News hub: title built from translations, hreflang tags - News article: title/description from DB, OG image = cover, type=article, publishedTime + modifiedTime for date freshness signals - Applications: title/description from DB, type=product, hero image - Heritage: localized title/description JSON-LD STRUCTURED DATA - Site-wide (in root layout): Organization (with HQ address, founder, contact, social profiles) + WebSite β€” drives Google knowledge panel - Article pages: Article schema with publisher/datePublished/dateModified β€” required for Google News / Discover eligibility - Application pages: Product schema (FLUX brand, RF Industrial category) + BreadcrumbList β€” drives rich-snippet breadcrumb in search results NOTES - Open Graph metadataBase set from NEXT_PUBLIC_APP_URL so absolute URLs for OG images are correct (LinkedIn previews require absolute paths) - All pages have canonical URLs to prevent duplicate-content penalties - /parts already has noindex meta (B2B portal) β€” also blocked in robots - No DB schema changes. Pure additions to /src/lib and /src/app. --- src/app/[locale]/applications/[slug]/page.tsx | 66 +++++- src/app/[locale]/heritage/page.tsx | 17 ++ src/app/[locale]/layout.tsx | 34 ++- src/app/[locale]/news/[slug]/page.tsx | 58 ++++- src/app/[locale]/news/page.tsx | 17 ++ src/app/[locale]/page.tsx | 38 +++- src/app/robots.ts | 30 +++ src/app/sitemap.ts | 104 +++++++++ src/components/seo/JsonLd.tsx | 22 ++ src/lib/seo.ts | 201 ++++++++++++++++++ 10 files changed, 574 insertions(+), 13 deletions(-) create mode 100644 src/app/robots.ts create mode 100644 src/app/sitemap.ts create mode 100644 src/components/seo/JsonLd.tsx create mode 100644 src/lib/seo.ts diff --git a/src/app/[locale]/applications/[slug]/page.tsx b/src/app/[locale]/applications/[slug]/page.tsx index c3cbf36..56937e8 100644 --- a/src/app/[locale]/applications/[slug]/page.tsx +++ b/src/app/[locale]/applications/[slug]/page.tsx @@ -1,6 +1,7 @@ // ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. export const revalidate = 60; +import type { Metadata } from "next"; import Link from "next/link"; import fs from "fs"; import path from "path"; @@ -8,8 +9,14 @@ import { prisma } from "@/lib/prisma"; import { notFound } from "next/navigation"; import ApplicationClient from "./ApplicationClient"; -// πŸ”₯ IMPORTAMOS LA TUBERÍA MÁGICA import { getLocalizedData } from "@/lib/i18nHelper"; +import { + buildPageMetadata, + productSchema, + breadcrumbSchema, + baseUrl, +} from "@/lib/seo"; +import JsonLd from "@/components/seo/JsonLd"; // --- FUNCIΓ“N ORIGINAL PARA LEER IMÁGENES LOCALES --- function getApplicationImages(slug: string) { @@ -29,6 +36,39 @@ function getApplicationImages(slug: string) { return { heroImage, blueprints, machines }; } +// ── Per-page metadata (Open Graph, Twitter, hreflang, canonical) ─────────── +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string; locale: string }>; +}): Promise { + const { slug, locale } = await params; + + try { + const raw = await prisma.application.findUnique({ where: { slug } }); + if (!raw) { + return { + title: "Application not found | FLUX", + robots: { index: false, follow: false }, + }; + } + + const data = getLocalizedData(raw, locale); + const heroImage = getApplicationImages(slug).heroImage; + + return buildPageMetadata({ + locale, + pathWithoutLocale: `applications/${slug}`, + title: `${data.title} β€” RF Industrial Solutions | FLUX`, + description: data.shortDescription || data.subtitle, + ogImageUrl: heroImage || undefined, + type: "product", + }); + } catch { + return { title: "FLUX | Energy, Directed." }; + } +} + // GENERACIΓ“N DE RUTAS ESTÁTICAS DESDE LA BD export async function generateStaticParams() { // In production Docker build, DB is not available. @@ -85,6 +125,26 @@ export default async function ApplicationPage({ params }: { params: Promise<{ sl // 3. Leemos las imΓ‘genes de la carpeta original const images = getApplicationImages(slug); - // Pasamos TODO al componente cliente interactivo (que ya viene traducido) - return ; + const url = `${baseUrl()}/${locale}/applications/${slug}`; + const jsonLd = [ + productSchema({ + name: data.title, + description: data.shortDescription || data.subtitle, + imageUrl: images.heroImage || undefined, + category: data.category, + url, + }), + breadcrumbSchema([ + { name: "Home", url: `${baseUrl()}/${locale}` }, + { name: "Applications", url: `${baseUrl()}/${locale}#applications-deep` }, + { name: data.title, url }, + ]), + ]; + + return ( + <> + + + + ); } diff --git a/src/app/[locale]/heritage/page.tsx b/src/app/[locale]/heritage/page.tsx index b79b262..d6781d6 100644 --- a/src/app/[locale]/heritage/page.tsx +++ b/src/app/[locale]/heritage/page.tsx @@ -1,6 +1,7 @@ // ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. export const revalidate = 60; +import type { Metadata } from "next"; import Link from "next/link"; import Image from "next/image"; import { prisma } from "@/lib/prisma"; @@ -11,6 +12,22 @@ import AutoPlayVideo from "@/components/AutoPlayVideo"; // πŸ”₯ IMPORTACIONES DE IDIOMAS import { getLocalizedData } from "@/lib/i18nHelper"; import { getTranslations } from "next-intl/server"; +import { buildPageMetadata } from "@/lib/seo"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "HeritagePage" }); + return buildPageMetadata({ + locale, + pathWithoutLocale: "heritage", + title: `${t("subtitle")} β€” ${t("title1").trim()} ${t("title2").trim()} | FLUX`, + description: `${t("title1")} ${t("title2")} β€” Discover Patrizio Grando's 40-year legacy in Solid-State RF technology.`, + }); +} // ── SÚPER PARSER MARKDOWN (Con Tablas, ImΓ‘genes y Dark Mode puro) ── const renderMarkdown = (text: string) => { diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 36caaf3..68d268c 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -12,7 +12,9 @@ import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; import { notFound } from 'next/navigation'; import { routing } from '@/i18n/routing'; -import { getBranding } from '@/lib/siteSettings'; +import { getBranding, getSocialLinks } from '@/lib/siteSettings'; +import { organizationSchema, websiteSchema } from '@/lib/seo'; +import JsonLd from '@/components/seo/JsonLd'; const inter = Inter({ subsets: ["latin"] }); @@ -69,7 +71,13 @@ export default async function RootLayout({ notFound(); } - const messages = await getMessages(); + const [messages, branding, social] = await Promise.all([ + getMessages(), + getBranding(), + getSocialLinks(), + ]); + + const sameAs = [social.linkedin, social.instagram, social.youtube].filter(Boolean); return ( @@ -82,23 +90,33 @@ export default async function RootLayout({ position: "relative", }} > + {/* Site-wide JSON-LD: Organization + WebSite β€” picked up by Google + knowledge panel and rich snippets. */} + + - + - - {/* πŸ”₯ Panel del Carrito de Repuestos y Soporte TΓ©cnico πŸ”₯ */} + - {/* Inyectamos el manejador de transiciones aquΓ­ */} - +
{children}