perf: compress hero image and defer page transition shutter
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🔍 Prepare (push) Has been cancelled
This commit is contained in:
@@ -80,10 +80,11 @@ export function HeroVideo(props: HeroVideoProps) {
|
||||
{/* Background color while video loads */}
|
||||
<div className="absolute inset-0 z-0 bg-neutral-dark" />
|
||||
|
||||
{/* ALWAYS render a native img as the LCP element to avoid Render Delay */}
|
||||
<img
|
||||
key={`img-${pathname}-${posterSrc}`}
|
||||
src={posterSrc}
|
||||
srcSet={posterSrc.includes('-poster.webp') ? `${posterSrc.replace('-poster.webp', '-poster-mobile.webp')} 800w, ${posterSrc} 1920w` : undefined}
|
||||
sizes={posterSrc.includes('-poster.webp') ? "(max-width: 768px) 800px, 1920px" : undefined}
|
||||
alt={posterAlt}
|
||||
className="absolute inset-0 w-full h-full object-cover z-[1] pointer-events-none"
|
||||
decoding="sync"
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { LazyMotion } from 'framer-motion';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
|
||||
|
||||
// Defer loading the shutter to eliminate AnimatePresence from the initial bundle
|
||||
const DynamicPageTransitionShutter = dynamic(
|
||||
() => import('./PageTransitionShutter').then(mod => mod.PageTransitionShutter),
|
||||
{ ssr: false }
|
||||
);
|
||||
|
||||
interface TransitionContextType {
|
||||
isTransitioning: boolean;
|
||||
transitionMessage: string | null;
|
||||
@@ -17,8 +24,18 @@ const TransitionContext = createContext<TransitionContextType | undefined>(undef
|
||||
export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [transitionMessage, setTransitionMessage] = useState<string | null>(null);
|
||||
const [hasMountedShutter, setHasMountedShutter] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// Load the heavy shutter chunk in the background when the main thread is idle
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
|
||||
window.requestIdleCallback(() => setHasMountedShutter(true));
|
||||
} else {
|
||||
setTimeout(() => setHasMountedShutter(true), 2000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// We use a ref to track transition state without triggering the route change effect when it turns true
|
||||
const isTransitioningRef = React.useRef(isTransitioning);
|
||||
useEffect(() => {
|
||||
@@ -44,6 +61,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
setTransitionMessage(message);
|
||||
setIsTransitioning(true);
|
||||
setHasMountedShutter(true);
|
||||
|
||||
// Die Animation des Shutters abwarten (ca. 700ms), dann pushen
|
||||
setTimeout(() => {
|
||||
@@ -72,6 +90,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||
/>
|
||||
</React.Suspense>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
{hasMountedShutter && <DynamicPageTransitionShutter />}
|
||||
{children}
|
||||
</LazyMotion>
|
||||
</TransitionContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user