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.
Some technical terms were being translated literally and reading awkwardly
to industrial buyers — fixed across IT, ES, VEC, DE so they match the
English source and industry convention.
PRESERVED IN ENGLISH (industry-standard, never translate)
- "Solid-State RF" — was "RF a Stato Solido" / "RF de Estado Sólido" /
"RF a Stato Sołido" / "Solid-State-RF"
- "Microwave Systems" — was "Sistemi a Microonde" / "Sistemas de
Microondas" / "Mikrowellensysteme" / "Sistemi Microwave"
- "Radio Frequency (RF)" — was "Radiofrequenza" / "Radiofrecuencia" /
"Radiofrequensa" / "Hochfrequenztechnologie" (kept as the technical
proper noun, with the RF acronym in parentheses for first reference)
- "Pulse Wave" — was "onde pulsate" / "ondas pulsadas" / "onde pulsà" /
"Pulswellen-Technologie"
Files: messages/it.json, messages/es.json, messages/vec.json,
messages/de.json. messages/en.json unchanged. JSON syntax validated.
Adds a full settings dashboard at /hq-command/dashboard/settings so the
client can update favicon, logos, footer text, social links and OG image
without code changes — wired into the SiteSetting model from the previous
commit.
NEW
- src/lib/siteSettingsTypes.ts: pure types + defaults (client-safe import)
- src/lib/siteSettings.ts: server-only loader using the SiteSetting model
- /api/assets gains a "branding" flat scope that writes to /public/branding
- /hq-command/dashboard/settings/{actions.ts, page.tsx} with three tabs:
1. Branding — favicon, Apple touch icon, main logo, email logo, OG
image, theme color. Each field has helper text with recommended
size/format and live preview.
2. Footer — CTA banner, HQ address, legal links. Optional one-click
AI translation to IT, VEC, ES, DE.
3. Social — LinkedIn, Instagram, YouTube, contact email.
WIRED INTO LAYOUT
- src/app/[locale]/layout.tsx now uses generateMetadata + generateViewport
to pull favicon, OG image and theme color dynamically. Adds Twitter
Card metadata. Falls back to the default flux-logo when SiteSetting
table is empty.
- src/components/layout/Footer.tsx reads CTA/HQ/legal copy from DB,
supports per-locale overrides via translationsJson, and renders social
icons (LinkedIn / Instagram / YouTube / Mail) only for filled fields.
UX FOR THE EDITOR (David's "12-year-old test")
- Drop-zone uploaders next to URL inputs — paste-or-upload either way
- Live image previews next to every branding field
- "Saved — live in 60 seconds" inline confirmation, no extra modals
- Recommended sizes spelled out next to each field (e.g. "PNG, square,
minimum 512×512" for favicon)
- Tooltip explaining why each image is needed
NO SCHEMA CHANGES — uses the SiteSetting table created in the previous
commit. Existing rows untouched.
Replaces the filesystem-scan hero (fs.readdirSync of /public/footage/main)
with a fully CMS-driven HeroSlide model. Editors can now drag-drop reorder,
toggle slides on/off, set focal points for proper mobile cropping, and
auto-translate per-slide captions.
NEW SCHEMA (additive — does not touch existing tables)
- HeroSlide: mediaUrl, mediaType, altText, order, isActive, focalPointX,
focalPointY, translationsJson, timestamps
- SiteSetting: key-value JSON store for site-wide config (favicon, logo,
footer, OG image) — wired up in next commit
- Migration 20260504120000_add_hero_slides_and_site_settings/migration.sql
uses CREATE TABLE IF NOT EXISTS, additive only
HERO REEL REFACTOR (Bug #4 — responsive mobile/iPad)
- Switches from `images: string[]` to `slides: HeroSlideData[]` while
keeping a backwards-compat path so legacy callers still work
- w-screen → w-full max-w-[100vw] (no horizontal scroll on iOS)
- h-[100vh] → h-[100svh] so iOS Safari URL bar doesn't push content
- Reduces title font sizes on small viewports (text-3xl → text-4xl
→ text-5xl → text-[5.5rem]) so the headline stays inside the canvas
- objectPosition driven by focal-point fields per slide
- Native <video> support for video slides
HQ COMMAND — /hq-command/dashboard/hero
- Drag-drop reorder, click-to-set-focal-point, inline alt-text editing
- Auto-save with "Saving…" / "Saved ✓" indicators
- Per-slide caption overrides (title, subtitle, descriptions)
- Optional one-click AI translation to IT, VEC, ES, DE
- Drop-zone uploader → /api/assets (scope=footage, flat folder)
API — /api/assets
- New flat scopes: "footage" (writes to /public/footage/main) and
"branding" (writes to /public/branding) — slug-less for site-wide assets
- New buildPublicUrl helper centralises the URL convention
- Revalidate helper expanded with branding + settings scopes
HOME PAGE
- Reads hero slides from DB first; falls back to filesystem scan when
HeroSlide table is empty (so production keeps working immediately
after migration runs but before the editor populates rows)
DEPLOY NOTES
- After git pull on VPS, run the migration ONCE:
docker compose exec app npx prisma migrate deploy
Then:
docker compose up -d --build app
Existing data (AdminUser w/ 2FA, ClientUser, GlobalNode, Application,
TimelineEvent, NewsArticle, HeritageSection, SparePart, OperationsSignal,
NotificationRoute, PageContent) is NOT touched. Migration only creates
two new tables.
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.