cb7458cded
- Create Breadcrumbs.tsx server component — semantic <nav> + <ol>/<li> with aria-current, ChevronRight separators, Apple-clean styling - Add breadcrumbs to news article hero overlay (reuses JSON-LD crumbs) - Add breadcrumbs to application detail hero (passed as prop to client component) - Refactor breadcrumb data into shared array for JSON-LD + visual nav Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
195 lines
6.3 KiB
TypeScript
195 lines
6.3 KiB
TypeScript
// ISR: revalidate every 60s — see /[locale]/page.tsx for the full rationale.
|
|
export const revalidate = 60;
|
|
|
|
import type { Metadata } from "next";
|
|
import Link from "next/link";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { notFound } from "next/navigation";
|
|
import ApplicationClient from "./ApplicationClient";
|
|
|
|
import { setRequestLocale } from "next-intl/server";
|
|
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) {
|
|
let blueprints: string[] = [];
|
|
let machines: string[] = [];
|
|
let heroImage = "";
|
|
|
|
try {
|
|
const imagesDir = path.join(process.cwd(), "public", "applications", slug);
|
|
|
|
if (fs.existsSync(imagesDir)) {
|
|
const files = fs.readdirSync(imagesDir).filter((file) => /\.(png|jpe?g|webp)$/i.test(file));
|
|
|
|
heroImage = files[0] ? `/applications/${slug}/${files[0]}` : "";
|
|
blueprints = files
|
|
.filter((f) => f.includes("Screenshot") || f.startsWith("P10") || f.includes("blueprint"))
|
|
.slice(0, 3)
|
|
.map((f) => `/applications/${slug}/${f}`);
|
|
machines = files
|
|
.filter((f) => !f.includes("Screenshot") && !f.startsWith("P10") && !f.includes("blueprint"))
|
|
.slice(1, 4)
|
|
.map((f) => `/applications/${slug}/${f}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] Image scan failed:`, error);
|
|
}
|
|
|
|
return { heroImage, blueprints, machines };
|
|
}
|
|
|
|
// ── Per-page metadata (Open Graph, Twitter, hreflang, canonical) ───────────
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string; locale: string }>;
|
|
}): Promise<Metadata> {
|
|
try {
|
|
const { slug, locale } = await params;
|
|
const raw = await prisma.application.findUnique({ where: { slug } });
|
|
if (!raw) {
|
|
return {
|
|
title: "Application not found | FLUX",
|
|
robots: { index: false, follow: false },
|
|
};
|
|
}
|
|
|
|
const data: any = getLocalizedData(raw, locale);
|
|
const heroImage = getApplicationImages(slug).heroImage;
|
|
const title = data?.title || "Application";
|
|
const description = data?.shortDescription || data?.subtitle || "FLUX RF industrial solutions.";
|
|
|
|
return buildPageMetadata({
|
|
locale,
|
|
pathWithoutLocale: `applications/${slug}`,
|
|
title: `${title} — RF Industrial Solutions | FLUX`,
|
|
description,
|
|
ogImageUrl: heroImage || undefined,
|
|
type: "product",
|
|
});
|
|
} catch (error) {
|
|
console.error("[applications generateMetadata]", error);
|
|
return { title: "FLUX | Energy, Directed." };
|
|
}
|
|
}
|
|
|
|
// Pre-render all known application slugs at build time. New slugs added
|
|
// after deploy render on-demand and get cached by ISR (revalidate=60).
|
|
// try/catch ensures the build never fails if the DB is unreachable
|
|
// during docker build — pages just render on first request instead.
|
|
export async function generateStaticParams() {
|
|
try {
|
|
const apps = await prisma.application.findMany({
|
|
select: { slug: true },
|
|
});
|
|
return apps.map((app) => ({ slug: app.slug }));
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// 🔥 AHORA RECIBIMOS EL LOCALE DESDE LA URL
|
|
export default async function ApplicationPage({ params }: { params: Promise<{ slug: string, locale: string }> }) {
|
|
const resolvedParams = await params;
|
|
const { slug, locale } = resolvedParams;
|
|
setRequestLocale(locale);
|
|
|
|
// 1. Buscamos la Teoría General de la Aplicación
|
|
let rawData: any = null;
|
|
try {
|
|
rawData = await prisma.application.findUnique({ where: { slug } });
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] DB error fetching application:`, error);
|
|
}
|
|
|
|
if (!rawData) {
|
|
return (
|
|
<div className="min-h-screen flex flex-col items-center justify-center bg-[#F5F5F7] dark:bg-[#050505]">
|
|
<h1 className="text-2xl text-[#86868B] mb-4">Application not found in Database</h1>
|
|
<Link href={`/${locale}/#applications-deep`} className="text-[#00F0FF] hover:underline">Return Home</Link>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// 🔥 TRADUCIMOS LA APLICACIÓN PRINCIPAL
|
|
let data: any;
|
|
try {
|
|
data = getLocalizedData(rawData, locale);
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] Locale merge failed:`, error);
|
|
data = rawData;
|
|
}
|
|
|
|
// 2. Buscamos el "Muro de Soluciones" (Casos Reales específicos de esta app)
|
|
let rawRealCases: any[] = [];
|
|
try {
|
|
rawRealCases = await prisma.globalNode.findMany({
|
|
where: {
|
|
application: slug,
|
|
isActive: true,
|
|
projectOverview: { not: null },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] DB error fetching cases:`, error);
|
|
}
|
|
|
|
// 🔥 TRADUCIMOS TODOS LOS CASOS DE ESTUDIO DEL MURO
|
|
let realCases: any[] = [];
|
|
try {
|
|
realCases = rawRealCases.map((node: any) => getLocalizedData(node, locale));
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] Cases locale merge failed:`, error);
|
|
realCases = rawRealCases;
|
|
}
|
|
|
|
// 3. Leemos las imágenes de la carpeta original
|
|
const images = getApplicationImages(slug);
|
|
|
|
// 4. JSON-LD structured data — wrapped to never break the render.
|
|
const appTitle = data?.title || "FLUX Application";
|
|
const appUrl = `${baseUrl()}/${locale}/applications/${slug}`;
|
|
const crumbs = [
|
|
{ name: "Home", url: `/${locale}` },
|
|
{ name: "Applications", url: `/${locale}#applications-deep` },
|
|
{ name: appTitle, url: `/${locale}/applications/${slug}` },
|
|
];
|
|
|
|
let jsonLd: object[] = [];
|
|
try {
|
|
const description = data?.shortDescription || data?.subtitle || "";
|
|
jsonLd = [
|
|
productSchema({
|
|
name: appTitle,
|
|
description,
|
|
imageUrl: images.heroImage || undefined,
|
|
category: data?.category || "RF Industrial",
|
|
url: appUrl,
|
|
}),
|
|
breadcrumbSchema(
|
|
crumbs.map((c) => ({ name: c.name, url: `${baseUrl()}${c.url}` }))
|
|
),
|
|
];
|
|
} catch (error) {
|
|
console.error(`[applications/${slug}] JSON-LD build failed:`, error);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{jsonLd.length > 0 && <JsonLd data={jsonLd} />}
|
|
<ApplicationClient data={data} realCases={realCases} images={images} breadcrumbs={crumbs} />
|
|
</>
|
|
);
|
|
}
|