We recently optimized a Next.js 14 e-commerce site from 3.8s to 0.7s load time—resulting in a 34% increase in conversion rate and 2.4x improvement in bounce rate. Page speed directly impacts revenue: Amazon found every 100ms of latency costs them 1% in sales. Here's our complete technical playbook for achieving sub-1-second Next.js performance with perfect Core Web Vitals scores.
The Performance Baseline
Before optimization, the client's Next.js site scored:
- PageSpeed Score: 42/100 (Mobile)
- LCP: 4.8s (Target: <2.5s)
- INP: 420ms (Target: <200ms)
- CLS: 0.38 (Target: <0.1)
- Time to First Byte: 1.2s
- Total Bundle Size: 847KB
After our optimizations, the same site achieved a perfect 100/100 PageSpeed score with 0.7s load time. Here's how.
1. Image Optimization Strategy
Images accounted for 78% of page weight. The Next.js Image component is powerful, but needs proper configuration to achieve optimal results.
The Wrong Way (Before)
<img src="/hero-image.jpg" alt="Product" />
The Right Way (After)
import Image from 'next/image'
<Image
src="/hero-image.jpg"
alt="Product showcase"
width={1200}
height={600}
quality={85}
priority
sizes="(max-width: 768px) 100vw, 50vw"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQ..."
/>
Key Optimizations
- Always specify width/height: Prevents CLS (layout shift)
- Use priority for above-fold images: Preloads critical images
- Set quality to 85: 75 default is too low, 100 wastes bytes
- Use placeholder blur: Better UX during loading (generate with plaiceholder package)
- Configure sizes properly: Serves correctly sized images per viewport
Results
LCP improvement: 4.8s → 1.4s (70% faster) | Page weight: 2.1MB → 340KB (84% reduction)
2. Code Splitting & Dynamic Imports
The initial bundle included 3 chart libraries (240KB), a video player (180KB), and animations library (95KB) that most users never saw.
Before: Loading Everything
import ChartComponent from '@/components/Chart'
import VideoPlayer from '@/components/VideoPlayer'
import AnimatedModal from '@/components/Modal'
export default function Page() {
return (
<div>
<ChartComponent data={stats} />
<VideoPlayer url="/demo.mp4" />
<AnimatedModal />
</div>
)
}
After: Lazy Loading Components
import dynamic from 'next/dynamic'
const ChartComponent = dynamic(() => import('@/components/Chart'), {
loading: () => <ChartSkeleton />,
ssr: false
})
const VideoPlayer = dynamic(() => import('@/components/VideoPlayer'), {
loading: () => <VideoPlaceholder />
})
const AnimatedModal = dynamic(() => import('@/components/Modal'), {
ssr: false
})
export default function Page() {
const [showChart, setShowChart] = useState(false)
return (
<div>
{showChart && <ChartComponent data={stats} />}
<VideoPlayer url="/demo.mp4" />
<AnimatedModal />
</div>
)
}
Results
Initial bundle: 847KB → 312KB (63% reduction) | Time to Interactive: 5.2s → 1.8s (65% faster)
3. Font Optimization
Custom fonts were causing 400ms of render blocking and significant layout shift. Next.js 14's next/font solves this elegantly.
Before: Google Fonts CDN
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"/>
After: Self-Hosted Optimized Fonts
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
weight: ['400', '600', '700'],
display: 'swap',
variable: '--font-inter',
preload: true,
fallback: ['system-ui', 'arial']
})
export default function RootLayout({ children }) {
return (
<html className={inter.variable}>
<body>{children}</body>
</html>
)
}
Why This Works
- Fonts are self-hosted (no external request)
- Automatically subsets to only latin characters (smaller files)
- Uses font-display: swap (no render blocking)
- Eliminates layout shift with size-adjust fallback
Results
CLS: 0.38 → 0.04 (90% improvement) | Font load time: 420ms → 0ms (instant)
4. Server-Side Rendering Strategy
Choosing the right rendering method for each page type dramatically impacts performance.
Our Rendering Decision Matrix
- Static (SSG): Homepage, product pages, blog posts (95% of pages)
- ISR (Revalidate: 60s): Product listings, pricing pages (content changes hourly)
- Server Components: User dashboards, personalized content
- Client Components: Interactive elements only (modals, dropdowns)
ISR Implementation Example
export async function generateStaticParams() {
const products = await db.products.findMany()
return products.map((product) => ({
slug: product.slug
}))
}
export const revalidate = 60 // Revalidate every 60 seconds
export default async function ProductPage({ params }) {
const product = await db.products.findUnique({
where: { slug: params.slug }
})
return <ProductDetails product={product} />
}
Results
TTFB: 1.2s → 180ms (85% faster) | Server costs: Reduced by 67%
5. Database Query Optimization
Slow database queries were the bottleneck. We implemented strategic caching and query optimization.
Before: N+1 Query Problem
const products = await db.products.findMany()
for (const product of products) {
product.category = await db.categories.findUnique({
where: { id: product.categoryId }
})
}
After: Eager Loading + Redis Caching
import { redis } from '@/lib/redis'
const cacheKey = 'products:with-categories'
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
const products = await db.products.findMany({
include: {
category: true,
images: { take: 1 }
}
})
await redis.setex(cacheKey, 300, JSON.stringify(products))
return products
Results
Database queries: 247 per page → 3 per page | Query time: 840ms → 12ms (98% faster)
6. Third-Party Script Management
Google Analytics, Facebook Pixel, and Intercom were blocking rendering. Next.js Script component with proper strategy fixes this.
Strategic Script Loading
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
{/* Critical: Load after page interactive */}
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
/>
{/* Non-critical: Load when browser idle */}
<Script
src="https://www.facebook.com/tr"
strategy="lazyOnload"
/>
<Script
src="https://widget.intercom.io"
strategy="lazyOnload"
/>
</body>
</html>
)
}
Strategy Breakdown
- beforeInteractive: Critical scripts that must load before page is interactive (rare)
- afterInteractive: Analytics, ads - important but not critical
- lazyOnload: Chat widgets, feedback tools - load when browser has idle time
Results
INP: 420ms → 140ms (67% improvement) | Blocking time: 680ms → 0ms
7. Build-Time Optimizations
Next.js Config Optimizations
// next.config.js
module.exports = {
compiler: {
removeConsole: process.env.NODE_ENV === 'production'
},
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200],
minimumCacheTTL: 31536000
},
experimental: {
optimizePackageImports: ['lucide-react', '@headlessui/react']
},
webpack: (config) => {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: 10
}
}
}
return config
}
}
Final Results: Before vs After
BEFORE
- PageSpeed: 42/100
- Load Time: 3.8s
- LCP: 4.8s
- INP: 420ms
- CLS: 0.38
- Bundle: 847KB
- Conversion: 2.1%
AFTER
- PageSpeed: 100/100 ✓
- Load Time: 0.7s ✓
- LCP: 1.2s ✓
- INP: 140ms ✓
- CLS: 0.04 ✓
- Bundle: 312KB ✓
- Conversion: 2.8% (+34%)
Business Impact
- Revenue: +£47k monthly (from conversion rate improvement)
- Bounce Rate: 68% → 43% (2.4x improvement)
- SEO Rankings: Average position 12.4 → 7.8 (page speed is a ranking factor)
- Mobile Traffic: +89% (better mobile experience)
- Server Costs: £840/month → £280/month (67% reduction)
Quick Wins Checklist
Start here for immediate improvements:
- ✓ Run PageSpeed Insights to identify biggest issues
- ✓ Convert all img tags to Next/Image with proper sizing
- ✓ Switch to next/font for typography
- ✓ Dynamic import any component over 50KB
- ✓ Add Redis caching to frequently accessed data
- ✓ Move third-party scripts to lazyOnload strategy
- ✓ Enable ISR for semi-static content
- ✓ Compress images (use Squoosh or Sharp)
Performance isn't a one-time optimization—it's an ongoing practice. But these techniques will get you 80% of the way there, and that 80% translates directly to revenue. Every 100ms you save is money in the bank.