fix: restore ISR on public pages — isolate DYNAMIC_SERVER_USAGE root cause
Deploy to VPS / deploy (push) Has been cancelled

Root cause: next-intl's getMessages/getTranslations internally resolves
requestLocale by reading cookies/headers, which trips DYNAMIC_SERVER_USAGE
under ISR. Fixed by calling setRequestLocale(locale) in layout + every
public page — caches the locale in React cache so next-intl never reads
cookies.

Changes:
- [locale]/layout.tsx: +setRequestLocale, +generateStaticParams (5 locales),
  wrap NavigationManager in <Suspense> (uses useSearchParams)
- 5 public pages: force-dynamic → revalidate=60, +setRequestLocale
- HQ dashboard pages: unchanged (still force-dynamic for auth)

Build verified: home/heritage/news pre-render as SSG with 1m revalidation,
slug pages render on-demand with ISR cache. Nginx s-maxage=60 remains as
safety net. Zero DYNAMIC_SERVER_USAGE errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 14:06:20 -05:00
parent 59a146ef10
commit ce8a13d7f8
6 changed files with 47 additions and 29 deletions
@@ -1,5 +1,5 @@
// Force-dynamic — see /[locale]/page.tsx for the rationale.
export const dynamic = "force-dynamic";
// 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";
@@ -9,6 +9,7 @@ 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,
@@ -82,16 +83,15 @@ export async function generateMetadata({
}
}
// generateStaticParams intentionally omitted — combined with
// `dynamic = "force-dynamic"`, this guarantees pure SSR per request.
// Having it (even returning [] in prod) made Next.js classify the
// route as SSG-eligible, which conflicted with dynamic API reads
// elsewhere in the tree and surfaced as DYNAMIC_SERVER_USAGE errors.
// generateStaticParams omitted — slug pages render on-demand and are cached
// by ISR (revalidate=60). The DYNAMIC_SERVER_USAGE issue that previously
// blocked this is now fixed via setRequestLocale.
// 🔥 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;
+5 -4
View File
@@ -1,5 +1,6 @@
// Force-dynamic — see /[locale]/page.tsx for the rationale.
export const dynamic = "force-dynamic";
// ISR: revalidate every 60s — see /[locale]/page.tsx for the full rationale.
// setRequestLocale caches the locale so next-intl doesn't read cookies/headers.
export const revalidate = 60;
import type { Metadata } from "next";
import Link from "next/link";
@@ -11,7 +12,7 @@ import AutoPlayVideo from "@/components/AutoPlayVideo";
// 🔥 IMPORTACIONES DE IDIOMAS
import { getLocalizedData } from "@/lib/i18nHelper";
import { getTranslations } from "next-intl/server";
import { getTranslations, setRequestLocale } from "next-intl/server";
import { buildPageMetadata } from "@/lib/seo";
export async function generateMetadata({
@@ -197,7 +198,7 @@ export default async function HeritagePage({ params }: { params: Promise<{ local
const resolvedParams = await params;
const locale = resolvedParams.locale;
// Pass locale explicitly so getTranslations stays static-friendly under ISR.
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: "HeritagePage" });
let rawSections: any[] = [];
+16 -2
View File
@@ -1,4 +1,5 @@
import type { Metadata, Viewport } from "next";
import { Suspense } from "react";
import { Inter } from "next/font/google";
import "../globals.css";
@@ -9,9 +10,15 @@ import Footer from "@/components/layout/Footer";
import CartDrawer from "@/components/layout/CartDrawer";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
// Pre-render all 5 locale variants at build time so Next.js knows which
// [locale] segments are valid. Required for ISR to work with next-intl.
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
import { getBranding, getSocialLinks } from '@/lib/siteSettings';
import { organizationSchema, websiteSchema } from '@/lib/seo';
import JsonLd from '@/components/seo/JsonLd';
@@ -116,6 +123,10 @@ export default async function RootLayout({
notFound();
}
// Cache the locale so next-intl resolves it from memory instead of
// reading cookies/headers — this is what allows ISR on child pages.
setRequestLocale(locale);
const [messages, branding, social] = await Promise.all([
getMessages({ locale }),
getBranding(),
@@ -153,7 +164,10 @@ export default async function RootLayout({
<CartDrawer />
<NavigationManager />
{/* Suspense boundary required for useSearchParams() under ISR */}
<Suspense fallback={null}>
<NavigationManager />
</Suspense>
<div className="flex-grow w-full flex flex-col relative">
{children}
+7 -4
View File
@@ -1,5 +1,5 @@
// Force-dynamic — see /[locale]/page.tsx for the rationale.
export const dynamic = "force-dynamic";
// 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";
@@ -8,6 +8,7 @@ import { prisma } from "@/lib/prisma";
import { ArrowLeft, Calendar, Tag, Linkedin } from "lucide-react";
import BreathingField from "@/components/visuals/BreathingField";
import { setRequestLocale } from "next-intl/server";
import { getLocalizedData } from "@/lib/i18nHelper";
import {
buildPageMetadata,
@@ -47,8 +48,9 @@ export async function generateMetadata({
}
}
// generateStaticParams intentionally omitted — see /[locale]/page.tsx
// for the rationale.
// generateStaticParams omitted — slug pages render on-demand and are cached
// by ISR (revalidate=60). The DYNAMIC_SERVER_USAGE issue is fixed via
// setRequestLocale.
// ── 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) ...
@@ -213,6 +215,7 @@ const renderMarkdown = (text: string) => {
export default async function ArticlePage({ params }: { params: Promise<{ slug: string, locale: string }> }) {
const resolvedParams = await params;
const { slug, locale } = resolvedParams;
setRequestLocale(locale);
let rawArticle: any = null;
try {
+4 -4
View File
@@ -6,11 +6,11 @@ import { Newspaper, ArrowRight, Calendar } from "lucide-react";
import BreathingField from "@/components/visuals/BreathingField";
import { getLocalizedData } from "@/lib/i18nHelper";
import { getTranslations } from "next-intl/server";
import { getTranslations, setRequestLocale } from "next-intl/server";
import { buildPageMetadata } from "@/lib/seo";
// Force-dynamic — see /[locale]/page.tsx for the rationale.
export const dynamic = "force-dynamic";
// ISR: revalidate every 60s — see /[locale]/page.tsx for the full rationale.
export const revalidate = 60;
export async function generateMetadata({
params,
@@ -31,7 +31,7 @@ export default async function NewsHub({ params }: { params: Promise<{ locale: st
const resolvedParams = await params;
const locale = resolvedParams.locale;
// Pass locale explicitly so getTranslations stays static-friendly under ISR.
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: "NewsHub" });
let rawArticles: any[] = [];
+8 -8
View File
@@ -15,17 +15,16 @@ import ApplicationsDeep from "@/components/sections/ApplicationsDeep";
import HeroReel from "@/components/sections/HeroReel";
import WhatWeDo from "@/components/sections/WhatWeDo";
import { setRequestLocale } from "next-intl/server";
import { buildPageMetadata } from "@/lib/seo";
import { getBranding } from "@/lib/siteSettings";
// Force-dynamic — there's an interaction between next-intl 4, Prisma client,
// and Next.js 16 standalone that intermittently leaks dynamic API reads
// (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";
// ISR: revalidate every 60s. The DYNAMIC_SERVER_USAGE issue was caused by
// next-intl internally reading cookies/headers to resolve the locale.
// Fixed by calling setRequestLocale(locale) which caches the locale in
// React cache, preventing the dynamic API read. Nginx also caches with
// s-maxage=60 + stale-while-revalidate=300 as a safety net.
export const revalidate = 60;
const TITLES: Record<string, string> = {
en: "FLUX | Solid-State RF Industrial Solutions",
@@ -63,6 +62,7 @@ export async function generateMetadata({
// ✅ Next.js 16: params es Promise y DEBE ser awaiteado
export default async function Home({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
setRequestLocale(locale);
// --- 1. SLIDES DEL HERO (CMS-managed via HeroSlide model, fallback to filesystem) ---
let heroSlides: Array<{