From 670eeb450c2b2ef332ffb4b0675c9d5703ed68f5 Mon Sep 17 00:00:00 2001 From: vibemarketerpromax Date: Thu, 19 Feb 2026 17:15:27 +0530 Subject: [PATCH] perf: Fix LCP delay and remove hardcoded polyfill bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove opacity:0 initial state from hero CSS animations. The browser won't count invisible elements as LCP, so setting opacity:0 in CSS delayed LCP from 1.2s (FCP) to 2.9s (animation completion). Using animation-fill-mode: both lets keyframes handle the 0→1 transition without an explicit initial invisible state. - Filter out Next.js hardcoded polyfill-nomodule CopyFilePlugin. Next.js unconditionally bundles a 68 KiB polyfill chunk (Array.at, flat, Object.fromEntries, etc.) via CopyFilePlugin regardless of browserslist. Our targets (Chrome 109+, Safari 17+, Firefox 115+) don't need these. The previous polyfill-module alias only removed the smaller 1.4 KiB file. --- app/globals.css | 21 +++++++++------------ next.config.mjs | 10 ++++++++++ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/globals.css b/app/globals.css index c7caf94..bc41302 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1173,7 +1173,10 @@ html:not(.dark) .hero-grid-pattern { opacity: 0.06; } -/* Hero fade-in animations - CSS-based for server-rendered content */ +/* Hero fade-in animations - CSS-based for server-rendered content. + IMPORTANT: No opacity:0 initial state. Setting opacity:0 in CSS delays LCP + because the browser won't count invisible elements as Largest Contentful Paint. + The keyframes handle the full transition from 0→1 without needing an initial state. */ @keyframes heroFadeIn { from { opacity: 0; @@ -1186,9 +1189,7 @@ html:not(.dark) .hero-grid-pattern { } .hero-fade-in { - animation: heroFadeIn 0.4s ease-out forwards; - animation-delay: 0.1s; - opacity: 0; + animation: heroFadeIn 0.4s ease-out 0.1s both; } .hero-fade-in-delay { @@ -1202,8 +1203,7 @@ html:not(.dark) .hero-grid-pattern { } .hero-glow-fade { - animation: glowFadeIn 1.2s ease-out forwards; - opacity: 0; + animation: glowFadeIn 1.2s ease-out both; } /* Stats slide-up animation */ @@ -1219,13 +1219,11 @@ html:not(.dark) .hero-grid-pattern { } .hero-stats-container { - animation: heroSlideUp 0.4s ease-out 0.15s forwards; - opacity: 0; + animation: heroSlideUp 0.4s ease-out 0.15s both; } .hero-stat-item { - animation: heroSlideUp 0.3s ease-out forwards; - opacity: 0; + animation: heroSlideUp 0.3s ease-out both; } .hero-stat-item:nth-child(1) { animation-delay: 0.15s; } @@ -1234,8 +1232,7 @@ html:not(.dark) .hero-grid-pattern { /* Children content fade-in */ .hero-children-fade { - animation: heroFadeIn 0.5s ease-out 0.25s forwards; - opacity: 0; + animation: heroFadeIn 0.5s ease-out 0.25s both; } /* Hover state overrides for navigation */ diff --git a/next.config.mjs b/next.config.mjs index 7441ab2..f6fdbb9 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -41,6 +41,16 @@ const nextConfig = { 'next/dist/build/polyfills/polyfill-module': false, }; + // Remove the hardcoded polyfill-nomodule CopyFilePlugin. + // Next.js unconditionally bundles a 68 KiB polyfill-nomodule chunk via CopyFilePlugin + // regardless of browserslist. Filter it out since our targets don't need it. + config.plugins = config.plugins.filter((plugin) => { + if (plugin.constructor.name === 'CopyFilePlugin' && plugin.filePath) { + return !plugin.filePath.includes('polyfill-nomodule'); + } + return true; + }); + // Merge splitChunks with Next.js defaults instead of replacing them const existingSplitChunks = config.optimization.splitChunks || {}; config.optimization.splitChunks = {