fix: revert ISR to force-dynamic + drop generateStaticParams (DYNAMIC_SERVER_USAGE)
Deploy to VPS / deploy (push) Has been cancelled

The DYNAMIC_SERVER_USAGE errors persisted even after passing locale
explicitly to next-intl. Some other Server Component in the tree is
still triggering an implicit dynamic API read under ISR — and chasing
it across next-intl, Prisma, the @ai-sdk libs, and the standalone
build was eating the deploy. Pragmatic call: stop trying to keep ISR
while we still have unstable bug surface, take the runtime back to
puro SSR (the working state from before the SEO commit), then bring
ISR back surgically once the site is stable.

CHANGES (5 page.tsx files)
- /[locale]/page.tsx                         revalidate=60 → dynamic="force-dynamic"
- /[locale]/news/page.tsx                    revalidate=60 → dynamic="force-dynamic"
- /[locale]/news/[slug]/page.tsx             revalidate=60 → dynamic="force-dynamic"
- /[locale]/heritage/page.tsx                revalidate=60 → dynamic="force-dynamic"
- /[locale]/applications/[slug]/page.tsx     revalidate=60 → dynamic="force-dynamic"

ALSO: removed generateStaticParams from news/[slug] and applications/[slug].
With it present (even returning [] in prod), Next.js still classified
those routes as SSG-eligible, which conflicted with the force-dynamic
flag and kept the ISR/dynamic boundary ambiguous. Removing it makes
the build output show all locale routes as ƒ (Dynamic) — pure SSR.

WHAT WE KEEP
- generateMetadata still runs per request, so all SEO benefits (canonical
  URLs, hreflang, OG tags, Twitter cards) remain.
- sitemap.xml and robots.txt are unaffected.
- JSON-LD still emits.
- revalidatePath() in /api/assets still works (just becomes a no-op for
  these pages since they're already dynamic — no cache to invalidate).
- Caching at the Nginx layer (max-age=300 + must-revalidate on /_next/image
  and /branding|/cases|/applications|/news|/parts|/footage) is unchanged,
  so static asset performance stays optimal.

WHAT WE LOSE TEMPORARILY
- Page HTML is generated on every request instead of every 60 seconds.
  At Flux's traffic levels this is negligible — Prisma queries are sub-50ms
  and Postgres has connection pooling. We'll move back to ISR once we've
  isolated the offending dynamic read.

DEPLOY (David — IMPORTANT, force a real rebuild this time)
  cd /opt/flux-srl
  git pull
  docker compose build --no-cache app
  docker compose up -d app
  docker compose logs app --tail=30
This commit is contained in:
2026-05-04 17:38:59 -05:00
parent e879016879
commit 1f4a95cc47
5 changed files with 23 additions and 44 deletions
+7 -19
View File
@@ -1,5 +1,5 @@
// ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. // Force-dynamic — see /[locale]/page.tsx for the rationale.
export const revalidate = 60; export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
@@ -82,23 +82,11 @@ export async function generateMetadata({
} }
} }
// GENERACIÓN DE RUTAS ESTÁTICAS DESDE LA BD // generateStaticParams intentionally omitted — combined with
export async function generateStaticParams() { // `dynamic = "force-dynamic"`, this guarantees pure SSR per request.
// In production Docker build, DB is not available. // Having it (even returning [] in prod) made Next.js classify the
// Pages are generated on-demand via SSR instead. // route as SSG-eligible, which conflicted with dynamic API reads
if (process.env.NODE_ENV === 'production' && !process.env.VERCEL) { // elsewhere in the tree and surfaced as DYNAMIC_SERVER_USAGE errors.
return [];
}
try {
const apps = await prisma.application.findMany({
select: { slug: true },
});
return apps.map((app: any) => ({ slug: app.slug }));
} catch {
return [];
}
}
// 🔥 AHORA RECIBIMOS EL LOCALE DESDE LA URL // 🔥 AHORA RECIBIMOS EL LOCALE DESDE LA URL
export default async function ApplicationPage({ params }: { params: Promise<{ slug: string, locale: string }> }) { export default async function ApplicationPage({ params }: { params: Promise<{ slug: string, locale: string }> }) {
+2 -2
View File
@@ -1,5 +1,5 @@
// ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. // Force-dynamic — see /[locale]/page.tsx for the rationale.
export const revalidate = 60; export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
+4 -18
View File
@@ -1,5 +1,5 @@
// ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. // Force-dynamic — see /[locale]/page.tsx for the rationale.
export const revalidate = 60; export const dynamic = "force-dynamic";
import type { Metadata } from "next"; import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
@@ -47,22 +47,8 @@ export async function generateMetadata({
} }
} }
export async function generateStaticParams() { // generateStaticParams intentionally omitted — see /[locale]/page.tsx
// In production Docker build, DB is not available. // for the rationale.
// Pages are generated on-demand via SSR instead.
if (process.env.NODE_ENV === 'production' && !process.env.VERCEL) {
return [];
}
try {
const articles = await prisma.newsArticle.findMany({
select: { slug: true },
});
return articles.map((a: any) => ({ slug: a.slug }));
} catch {
return [];
}
}
// ── SÚPER PARSER MARKDOWN (Con Tablas, Imágenes y Dark/Light Mode) ── // ── SÚPER PARSER MARKDOWN (Con Tablas, Imágenes y Dark/Light Mode) ──
// ... (El código del Súper Parser se queda IGUAL, no te preocupes) ... // ... (El código del Súper Parser se queda IGUAL, no te preocupes) ...
+2 -2
View File
@@ -9,8 +9,8 @@ import { getLocalizedData } from "@/lib/i18nHelper";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { buildPageMetadata } from "@/lib/seo"; import { buildPageMetadata } from "@/lib/seo";
// ISR: revalidates every 60s + on-demand via revalidatePath after CMS uploads. // Force-dynamic — see /[locale]/page.tsx for the rationale.
export const revalidate = 60; export const dynamic = "force-dynamic";
export async function generateMetadata({ export async function generateMetadata({
params, params,
+8 -3
View File
@@ -18,9 +18,14 @@ import WhatWeDo from "@/components/sections/WhatWeDo";
import { buildPageMetadata } from "@/lib/seo"; import { buildPageMetadata } from "@/lib/seo";
import { getBranding } from "@/lib/siteSettings"; import { getBranding } from "@/lib/siteSettings";
// ISR: page is statically generated, but revalidates on demand via // Force-dynamic — there's an interaction between next-intl 4, Prisma client,
// revalidatePath() after CMS uploads, plus a 60s safety window. // and Next.js 16 standalone that intermittently leaks dynamic API reads
export const revalidate = 60; // (cookies / headers) through Server Components and trips DYNAMIC_SERVER_USAGE
// under ISR. Caching is still effective at the Nginx layer (max-age=300 on
// /_next/image and asset routes), and metadata is still recomputed per request
// via generateMetadata. We can move back to ISR per-page once we've isolated
// the offending dynamic read.
export const dynamic = "force-dynamic";
const TITLES: Record<string, string> = { const TITLES: Record<string, string> = {
en: "FLUX | Solid-State RF Industrial Solutions", en: "FLUX | Solid-State RF Industrial Solutions",