Commit Graph

10 Commits

Author SHA1 Message Date
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
davidherran 7502c9c674 feat: generateStaticParams for application + news slug pages
Deploy to VPS / deploy (push) Has been cancelled
Pre-render all known slugs at build time so first visits are instant
from cache. 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
fall back to on-demand rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-06 14:39:57 -05:00
davidherran ce8a13d7f8 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>
2026-05-06 14:06:20 -05:00
davidherran 1f4a95cc47 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
2026-05-04 17:38:59 -05:00
davidherran 3d066fa67e fix: error boundaries + defensive try/catch on dynamic pages
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.
2026-05-04 16:45:37 -05:00
davidherran 01a84edee9 fix: prisma migrate now runs at container startup + dotenv optional
Deploy to VPS / deploy (push) Has been cancelled
Two related fixes for the deploy pipeline so DB schema changes never
again leave the site half-deployed.

PRISMA CONFIG (prisma.config.ts)
- "import 'dotenv/config'" was hard-required, but dotenv isn't installed
  in the production runtime image (env vars come from docker-compose).
- Wrapped in try/catch so it loads .env locally and silently no-ops in
  the container — `prisma migrate deploy` works in both environments.

DOCKERFILE
- Copies node_modules/prisma + prisma.config.ts to the runner stage so
  the CLI is available at runtime, not just at build.
- New CMD runs `prisma migrate deploy` before booting the server.
  Idempotent — already-applied migrations are skipped. If the DB is
  unreachable, the container exits and docker-compose retries.
- This means: from now on, `git pull && docker compose up -d --build app`
  is the entire deploy. No more "did you remember to run migrations?".

DEFENSIVE TRY/CATCH (applications/[slug]/page.tsx)
- prisma.application.findUnique and prisma.globalNode.findMany now have
  try/catch with logged errors. A transient DB hiccup or missing
  Application slug now degrades gracefully (renders "not found" or empty
  cases wall) instead of triggering a 500 Internal Server Error.

DEPLOY (David, this is the recovery sequence on the VPS)
  cd /opt/flux-srl
  git pull
  docker compose up -d --build app
  # The container will run pending migrations on its own.
  # No need to run `prisma migrate deploy` manually anymore.
2026-05-04 15:04:35 -05:00
davidherran 09e6d0c7cf seo: dynamic sitemap + robots + per-page metadata + JSON-LD
Brings the site up to enterprise SEO standards. Google now gets a complete
machine-readable map of the content, with multilingual hreflang tags,
structured data for the knowledge panel, and rich Open Graph cards on
LinkedIn / WhatsApp / Twitter.

NEW
- src/app/sitemap.ts: dynamic sitemap.xml from Prisma. Emits 5 locales x
  every active application + every active news article, with hreflang
  alternates linking each translation. Hourly revalidation.
- src/app/robots.ts: robots.txt blocks /hq-command/, /api/, /parts (B2B
  auth-gated), points crawlers at the sitemap.
- src/lib/seo.ts: helpers for canonical URLs, hreflang alternates, and
  JSON-LD schemas (Organization, WebSite, Article, Product, BreadcrumbList).
- src/components/seo/JsonLd.tsx: server component that emits one
  application/ld+json script tag per page.

PER-PAGE generateMetadata
- Home: localized titles + descriptions in EN/IT/VEC/ES/DE
- News hub: title built from translations, hreflang tags
- News article: title/description from DB, OG image = cover, type=article,
  publishedTime + modifiedTime for date freshness signals
- Applications: title/description from DB, type=product, hero image
- Heritage: localized title/description

JSON-LD STRUCTURED DATA
- Site-wide (in root layout): Organization (with HQ address, founder,
  contact, social profiles) + WebSite — drives Google knowledge panel
- Article pages: Article schema with publisher/datePublished/dateModified
  — required for Google News / Discover eligibility
- Application pages: Product schema (FLUX brand, RF Industrial category)
  + BreadcrumbList — drives rich-snippet breadcrumb in search results

NOTES
- Open Graph metadataBase set from NEXT_PUBLIC_APP_URL so absolute URLs
  for OG images are correct (LinkedIn previews require absolute paths)
- All pages have canonical URLs to prevent duplicate-content penalties
- /parts already has noindex meta (B2B portal) — also blocked in robots
- No DB schema changes. Pure additions to /src/lib and /src/app.
2026-05-04 14:42:43 -05:00
davidherran 6e46808c27 fix: instant CMS uploads + heritage dark/light + ISR caching
Eliminates the need to run "docker compose build" after uploading
images via HQ Command. Heritage page now respects light/dark mode.

CACHE INVALIDATION
- New helper src/lib/revalidate.ts called from /api/assets and
  /api/public-upload after every upload, delete, folder create
- Pages switch from force-dynamic to ISR with revalidate=60
  (regenerated on demand whenever content changes, plus 60s safety)
- Nginx now sends "max-age=300, must-revalidate" instead of "expires 30d"
  on /cases/, /applications/, /news/, /parts/, /footage/, /operations-inbox/
  so browsers revalidate via If-Modified-Since (304s on unchanged files)
- Next.js Image Optimizer aligned with same TTL via minimumCacheTTL=300
  and adds /_next/image location block in Nginx for correct headers

HERITAGE DARK/LIGHT FIX (Bug #8)
- Replaces hardcoded #0A0A0C / #00F0FF / text-white with proper
  light + dark variants throughout markdown renderer (tables, lists,
  headings, blockquotes, paragraphs, images)
- Hero section, navigation pill, and CMS-driven sections now switch
  with the global theme toggle

SECURITY HARDENING
- Server actions bodySizeLimit reduced from 500MB to 50MB
  (large uploads still go through /api/assets which uses Nginx 500MB cap)

DEPLOY NOTES
- Run on VPS:
    git pull
    docker compose up -d --build app
    docker compose exec nginx nginx -s reload
- No DB schema changes in this commit. Existing 2FA users / data untouched.
2026-05-04 09:27:46 -05:00
davidherran 0118774e31 production: docker fixes, nginx SSL config, generateStaticParams fallback
Deploy to VPS / deploy (push) Has been cancelled
2026-04-01 16:57:22 +00:00
davidherran fc24313f15 production: docker + nginx config for rf-flux.com
Deploy to VPS / deploy (push) Has been cancelled
2026-03-20 13:46:05 -05:00