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}