Files
flux-srl/src/app/[locale]/applications/[slug]/page.tsx
T
davidherran cb7458cded feat(seo): visual breadcrumb navigation on article + application pages
- 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>
2026-05-06 18:10:49 -05:00

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} />
</>
);
}