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:
2895
.lighthouseci/lhr-1782911226698.html
Normal file
2895
.lighthouseci/lhr-1782911226698.html
Normal file
File diff suppressed because one or more lines are too long
11796
.lighthouseci/lhr-1782911226698.json
Normal file
11796
.lighthouseci/lhr-1782911226698.json
Normal file
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@ import { DynamicInitialLoader as InitialLoader } from '@/components/providers/Dy
|
|||||||
import { DynamicMobileBottomNav as MobileBottomNav } from '@/components/layout/DynamicMobileBottomNav';
|
import { DynamicMobileBottomNav as MobileBottomNav } from '@/components/layout/DynamicMobileBottomNav';
|
||||||
|
|
||||||
import { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';
|
import { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';
|
||||||
import { DynamicPageTransitionShutter as PageTransitionShutter } from '@/components/providers/DynamicPageTransitionShutter';
|
const corporateBackground = null; // Removed to prevent line shifting
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
@@ -192,7 +192,7 @@ export default async function Layout(props: {
|
|||||||
<TransitionProvider>
|
<TransitionProvider>
|
||||||
<CorporateBackground />
|
<CorporateBackground />
|
||||||
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
||||||
<PageTransitionShutter />
|
{/* PageTransitionShutter loaded dynamically in TransitionProvider */}
|
||||||
<SkipLink />
|
<SkipLink />
|
||||||
<Header navLinks={navLinks} />
|
<Header navLinks={navLinks} />
|
||||||
<MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />
|
<MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />
|
||||||
|
|||||||
@@ -133,7 +133,12 @@ export default async function Home(props: { params: Promise<{ locale: string }>
|
|||||||
|
|
||||||
setRequestLocale(locale);
|
setRequestLocale(locale);
|
||||||
preload('/germany-map.svg', { as: 'image', fetchPriority: 'high' });
|
preload('/germany-map.svg', { as: 'image', fetchPriority: 'high' });
|
||||||
preload('/assets/videos/web/hero-kabelpflug-poster.webp', { as: 'image', fetchPriority: 'high' });
|
preload('/assets/videos/web/hero-kabelpflug-poster.webp', {
|
||||||
|
as: 'image',
|
||||||
|
fetchPriority: 'high',
|
||||||
|
imageSrcSet: '/assets/videos/web/hero-kabelpflug-poster-mobile.webp 800w, /assets/videos/web/hero-kabelpflug-poster.webp 1920w',
|
||||||
|
imageSizes: '(max-width: 768px) 800px, 1920px'
|
||||||
|
});
|
||||||
|
|
||||||
const mdx = await getMdxContent(locale, 'home');
|
const mdx = await getMdxContent(locale, 'home');
|
||||||
const allReferences = await getAllReferences(locale);
|
const allReferences = await getAllReferences(locale);
|
||||||
|
|||||||
@@ -80,10 +80,11 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
{/* Background color while video loads */}
|
{/* Background color while video loads */}
|
||||||
<div className="absolute inset-0 z-0 bg-neutral-dark" />
|
<div className="absolute inset-0 z-0 bg-neutral-dark" />
|
||||||
|
|
||||||
{/* ALWAYS render a native img as the LCP element to avoid Render Delay */}
|
|
||||||
<img
|
<img
|
||||||
key={`img-${pathname}-${posterSrc}`}
|
key={`img-${pathname}-${posterSrc}`}
|
||||||
src={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}
|
alt={posterAlt}
|
||||||
className="absolute inset-0 w-full h-full object-cover z-[1] pointer-events-none"
|
className="absolute inset-0 w-full h-full object-cover z-[1] pointer-events-none"
|
||||||
decoding="sync"
|
decoding="sync"
|
||||||
|
|||||||
@@ -3,9 +3,16 @@
|
|||||||
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
|
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
|
||||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||||
import { LazyMotion } from 'framer-motion';
|
import { LazyMotion } from 'framer-motion';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
|
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 {
|
interface TransitionContextType {
|
||||||
isTransitioning: boolean;
|
isTransitioning: boolean;
|
||||||
transitionMessage: string | null;
|
transitionMessage: string | null;
|
||||||
@@ -17,8 +24,18 @@ const TransitionContext = createContext<TransitionContextType | undefined>(undef
|
|||||||
export function TransitionProvider({ children }: { children: ReactNode }) {
|
export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||||
const [transitionMessage, setTransitionMessage] = useState<string | null>(null);
|
const [transitionMessage, setTransitionMessage] = useState<string | null>(null);
|
||||||
|
const [hasMountedShutter, setHasMountedShutter] = useState(false);
|
||||||
const router = useRouter();
|
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
|
// We use a ref to track transition state without triggering the route change effect when it turns true
|
||||||
const isTransitioningRef = React.useRef(isTransitioning);
|
const isTransitioningRef = React.useRef(isTransitioning);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,6 +61,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
setTransitionMessage(message);
|
setTransitionMessage(message);
|
||||||
setIsTransitioning(true);
|
setIsTransitioning(true);
|
||||||
|
setHasMountedShutter(true);
|
||||||
|
|
||||||
// Die Animation des Shutters abwarten (ca. 700ms), dann pushen
|
// Die Animation des Shutters abwarten (ca. 700ms), dann pushen
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -72,6 +90,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
|||||||
/>
|
/>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
<LazyMotion features={loadFeatures}>
|
<LazyMotion features={loadFeatures}>
|
||||||
|
{hasMountedShutter && <DynamicPageTransitionShutter />}
|
||||||
{children}
|
{children}
|
||||||
</LazyMotion>
|
</LazyMotion>
|
||||||
</TransitionContext.Provider>
|
</TransitionContext.Provider>
|
||||||
|
|||||||
BIN
public/assets/videos/web/hero-kabelpflug-poster-mobile.webp
Normal file
BIN
public/assets/videos/web/hero-kabelpflug-poster-mobile.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 110 KiB |
Reference in New Issue
Block a user