Hero, ScrollReveal und ProductCard starten mit opacity 0 und werden per
motion/react eingeblendet. Diese Startwerte stehen als inline-style bereits im
server-gerenderten HTML — auf wabenhain.com an 38 Elementen nachgezaehlt. Laeuft
kein JavaScript, blendet sie niemand wieder ein: der Besucher sieht das
Hero-Bild und darunter eine leere Seite. Kein Teilausfall, sondern der
komplette Inhalt.
Fuer Suchmaschinen war das unkritisch, der Text steht im HTML. Fuer Menschen
mit deaktiviertem JavaScript war es ein Totalausfall.
Die drei Bausteine tragen jetzt data-reveal, und ein noscript-Block im
Root-Layout hebt darueber die Startwerte auf. Das Attribut statt eines
Treffers auf [style*="opacity:0"] ist der Kern der Sache: Warenkorb-Schublade,
Menue und Cookie-Banner blenden ueber AnimatePresence ebenfalls mit opacity 0
ein, sollen ohne JavaScript aber verborgen BLEIBEN. Sie tragen das Attribut
nicht.
!important ist hier notwendig und nicht nachlaessig — einen inline-style
ueberschreibt sonst nichts.
Grenze der Loesung: noscript deckt "JavaScript deaktiviert" ab. Laedt das
Bundle dagegen und schlaegt erst spaeter fehl, greift es nicht; dafuer waere
ein Zeitgeber noetig, der die Animation im Normalfall stoeren wuerde.
tsc: 0 Fehler. vitest: 359/359 gruen. eslint: 0 Warnungen. next build: gruen.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Die Adresse der Seite war an zwei Stellen gepflegt: E-Mails nutzten
NEXT_PUBLIC_SITE_URL, alles SEO-Relevante dagegen ein fest verdrahtetes
"https://" + siteConfig.domain (16 Stellen, darunter eine roh eingetippte
"https://wabenhain.com" im Produkt-JSON-LD).
Laeuft der Shop unter einer anderen Domain als wabenhain.com — Staging,
Vorschau-Host, spaeterer Domainwechsel, der im k8s-Ingress hinterlegte Host
— zeigten damit sitemap.xml, robots.txt, saemtliche Canonical-Tags und alle
strukturierten Daten geschlossen auf eine fremde Adresse. Google verwirft
eine Sitemap mit fremden Hosts, und ein Canonical auf eine andere Domain
weist die Indexierung aktiv von der echten Seite weg.
Jetzt eine einzige Quelle: getSiteUrl() in lib/url.ts, mit
siteConfig.domain als Rueckfallebene, damit eine vergessene
Umgebungsvariable nicht "http://localhost:3000" in einer oeffentlichen
Sitemap landen laesst. getBaseUrl() bleibt als Alias fuer den in
E-Mail-Code eingebuergerten Namen und liefert denselben Wert.
robots.ts bekommt dabei "force-dynamic" (wie sitemap.ts es schon hatte):
ohne das rendert Next.js die Datei beim Build statisch vor und backt den
Wert ein, den NEXT_PUBLIC_SITE_URL genau dann hatte. Im Docker-Build ist
die Variable nicht gesetzt — die Sitemap-Zeile waere also dauerhaft auf dem
Rueckfallwert eingefroren. Gegen einen laufenden Produktions-Build
geprueft: robots.txt, sitemap.xml, Canonical-Tags und JSON-LD folgen jetzt
alle dem zur Laufzeit gesetzten Wert.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Datenbank (prisma/migrations/20260904120000_add_performance_indexes):
7 fehlende Indizes auf Fremdschluessel-/Filter-Spalten ergaenzt
(Order.customerId+createdAt, Order.status, Order.createdAt,
Order.discountCodeId, OrderItem.orderId, Address.customerId,
AuditLog.createdAt) — bislang volle Tabellenscans bei Bestelluebersicht,
Admin-Dashboard, Checkout-Gutscheinpruefung und Audit-Log.
Bundle-Groesse: Footer (TikTok/YouTube/Instagram-Icons, Copyright-Zeile)
aus dem client-seitigen site-chrome.tsx in eine eigene Server Component
(site-footer.tsx) ausgelagert — brauchte "use client" nur wegen
useI18n(), jetzt getDictionary()-Server-Aufruf wie auf jeder Admin-Seite.
Entfernt ungenutztes clientseitiges JS/Hydration aus jedem Seitenaufruf
(Footer steht im Root-Layout).
UX: app/not-found.tsx und app/error.tsx ergaenzt (Next.js-Konventionen
fuer 404 bzw. Laufzeitfehler) — beide fehlten komplett, unbekannte Routen
und Rendering-Fehler zeigten bisher Next.js' generische, unbrandete
Standardseiten statt einer zur Seite passenden Nutzerfuehrung.
Dictionary-Kollision dabei aufgefallen und aufgeloest: eine bereits
bestehende, admin-interne errorPage-Uebersetzung (src/app/admin/error.tsx,
seit dem urspruenglichen App-Grundgeruest vorhanden) blieb unangetastet,
die neue site-weite Fehlerseite nutzt einen eigenen Schluessel
(siteErrorPage).
Verifiziert: tsc --noEmit clean, 118/118 Tests gruen, pnpm build clean.
Co-Authored-By: Claude Sonnet 5 <[email protected]>