fix: error boundaries + defensive try/catch on dynamic pages
Deploy to VPS / deploy (push) Has been cancelled
Deploy to VPS / deploy (push) Has been cancelled
The /en/applications/digital-print page was still 500-ing after the
previous fixes. Without an error boundary, Next.js shows a generic
"Internal Server Error" with no detail — making remote diagnosis
require a `docker compose logs` round-trip every time.
ERROR BOUNDARIES (visible diagnostics)
- src/app/global-error.tsx: catches errors that bubble past every
route's error.tsx, including ones from the root layout. Renders
its own <html>/<body>.
- src/app/[locale]/error.tsx: locale-scoped boundary so the NavBar
and Footer keep rendering around the error UI. Shows the actual
error message + digest in a code block — much faster to diagnose
than a blank 500.
DEFENSIVE WRAPPING (every async + every transform)
- applications/[slug]/page.tsx
- getApplicationImages: try/catch around fs ops
- generateMetadata: full body wrapped, falls back to safe defaults
- getLocalizedData call wrapped (returns rawData if it throws)
- Cases query already had try/catch — adds same for the locale map
- JSON-LD build wrapped, falls back to empty array (still renders)
- Default fallbacks for title/description/category to avoid
productSchema receiving undefined fields
- news/[slug]/page.tsx
- prisma.newsArticle.findUnique now has try/catch
- getLocalizedData wrapped
- JSON-LD build wrapped, only rendered if non-empty
- publishedAt / updatedAt fallback to new Date() to avoid
"Invalid time value" from articleSchema's date conversion
The combination means: if the underlying bug is in any of the SEO
helpers, JSON-LD generation, or i18n merging, the page now degrades
gracefully and shows the actual error in the UI instead of 500-ing.
This commit is contained in:
@@ -20,17 +20,28 @@ import JsonLd from "@/components/seo/JsonLd";
|
||||
|
||||
// --- FUNCIÓN ORIGINAL PARA LEER IMÁGENES LOCALES ---
|
||||
function getApplicationImages(slug: string) {
|
||||
const imagesDir = path.join(process.cwd(), "public", "applications", slug);
|
||||
let blueprints: string[] = [];
|
||||
let machines: string[] = [];
|
||||
let heroImage = "";
|
||||
|
||||
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}`);
|
||||
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 };
|
||||
@@ -42,9 +53,8 @@ export async function generateMetadata({
|
||||
}: {
|
||||
params: Promise<{ slug: string; locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { slug, locale } = await params;
|
||||
|
||||
try {
|
||||
const { slug, locale } = await params;
|
||||
const raw = await prisma.application.findUnique({ where: { slug } });
|
||||
if (!raw) {
|
||||
return {
|
||||
@@ -53,18 +63,21 @@ export async function generateMetadata({
|
||||
};
|
||||
}
|
||||
|
||||
const data = getLocalizedData(raw, locale);
|
||||
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: `${data.title} — RF Industrial Solutions | FLUX`,
|
||||
description: data.shortDescription || data.subtitle,
|
||||
title: `${title} — RF Industrial Solutions | FLUX`,
|
||||
description,
|
||||
ogImageUrl: heroImage || undefined,
|
||||
type: "product",
|
||||
});
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error("[applications generateMetadata]", error);
|
||||
return { title: "FLUX | Energy, Directed." };
|
||||
}
|
||||
}
|
||||
@@ -110,7 +123,13 @@ export default async function ApplicationPage({ params }: { params: Promise<{ sl
|
||||
}
|
||||
|
||||
// 🔥 TRADUCIMOS LA APLICACIÓN PRINCIPAL
|
||||
const data = getLocalizedData(rawData, locale);
|
||||
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[] = [];
|
||||
@@ -128,30 +147,44 @@ export default async function ApplicationPage({ params }: { params: Promise<{ sl
|
||||
}
|
||||
|
||||
// 🔥 TRADUCIMOS TODOS LOS CASOS DE ESTUDIO DEL MURO
|
||||
const realCases = rawRealCases.map((node: any) => getLocalizedData(node, locale));
|
||||
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);
|
||||
|
||||
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 },
|
||||
]),
|
||||
];
|
||||
// 4. JSON-LD structured data — wrapped to never break the render.
|
||||
let jsonLd: object[] = [];
|
||||
try {
|
||||
const url = `${baseUrl()}/${locale}/applications/${slug}`;
|
||||
const title = data?.title || "FLUX Application";
|
||||
const description = data?.shortDescription || data?.subtitle || "";
|
||||
jsonLd = [
|
||||
productSchema({
|
||||
name: title,
|
||||
description,
|
||||
imageUrl: images.heroImage || undefined,
|
||||
category: data?.category || "RF Industrial",
|
||||
url,
|
||||
}),
|
||||
breadcrumbSchema([
|
||||
{ name: "Home", url: `${baseUrl()}/${locale}` },
|
||||
{ name: "Applications", url: `${baseUrl()}/${locale}#applications-deep` },
|
||||
{ name: title, url },
|
||||
]),
|
||||
];
|
||||
} catch (error) {
|
||||
console.error(`[applications/${slug}] JSON-LD build failed:`, error);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<JsonLd data={jsonLd} />
|
||||
{jsonLd.length > 0 && <JsonLd data={jsonLd} />}
|
||||
<ApplicationClient data={data} realCases={realCases} images={images} />
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user