DEV Community

Muhammad Zulqarnain
Muhammad Zulqarnain

Posted on

5 Next.js Performance Patterns That Cut Our Load Time by 60%

Performance is a feature.

At Quran.com, we serve 50 million users across 200+ countries on varying networks. A 1-second delay for users on slow connections meant people gave up. Every millisecond mattered.

Here are 5 patterns we implemented that cut load time by 60%. With code.

Pattern 1: Font Subsetting

The Problem: We served the full Amiri Arabic font (600KB) on every page. Users on 3G in Southeast Asia waited 4 seconds for the first render.

The Pattern: Subset fonts to only the Unicode ranges actually used on each page.

@font-face {
  font-family: 'Amiri';
  src: url('/fonts/amiri-subset.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+0600-06FF, U+200C, U+200D;
}
Enter fullscreen mode Exit fullscreen mode

The Result: Font size: 600KB → 48KB. Load time improvement: 40% on mobile.

pyftsubset Amiri-Regular.ttf --unicodes=0600-06FF,200C,200D --output-file=amiri-subset.woff2
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Incremental Static Regeneration (ISR)

The Problem: 114 surahs with 200+ translations. Full site generation: 45 minutes. Deploys blocked.

export async function getStaticProps(context) {
  const data = await fetchSurahData(context.params.surah);
  return {
    props: { data },
    revalidate: 3600,
  };
}

export async function getStaticPaths() {
  const paths = Array.from({ length: 114 }, (_, i) => ({
    params: { surah: String(i + 1) },
  }));
  return { paths, fallback: 'blocking' };
}
Enter fullscreen mode Exit fullscreen mode

On-demand revalidation:

export default async function handler(req, res) {
  if (req.query.secret !== process.env.REVALIDATE_SECRET) {
    return res.status(401).json({ message: 'Invalid token' });
  }
  await res.revalidate(`/surah/${req.query.surah}`);
  return res.json({ revalidated: true });
}
Enter fullscreen mode Exit fullscreen mode

The Result: Pages cached on CDN, served in <100ms globally.

Pattern 3: Dynamic Imports for Heavy Components

The Problem: Quran player: 350KB bundled. Every user downloads it even if they never use audio.

import dynamic from 'next/dynamic';

const QuranPlayer = dynamic(() => import('@/components/QuranPlayer'), {
  loading: () => <div>Loading player...</div>,
  ssr: false,
});

export default function SurahPage({ data }) {
  const [showPlayer, setShowPlayer] = useState(false);
  return (
    <div>
      <SurahText data={data} />
      {showPlayer && <QuranPlayer surah={data.surah} />}
      <button onClick={() => setShowPlayer(true)}>Play Audio</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Result: 68% of users never click play. They saved the download entirely.

Pattern 4: Image Optimization Pipeline

The Problem: Users upload 3-5MB JPEGs displayed at 120x120px.

import sharp from 'sharp';

export default async function handler(req, res) {
  const buffer = Buffer.from(req.body);
  const webpBuffer = await sharp(buffer).resize(400, 400, { fit: 'cover' }).webp({ quality: 80 }).toBuffer();
  const pngBuffer = await sharp(buffer).resize(400, 400, { fit: 'cover' }).png({ quality: 80 }).toBuffer();
  const webpUrl = await uploadToS3(webpBuffer, 'image.webp');
  const pngUrl = await uploadToS3(pngBuffer, 'image.png');
  res.json({ webpUrl, pngUrl });
}
Enter fullscreen mode Exit fullscreen mode
<picture>
  <source srcSet={image.webpUrl} type="image/webp" />
  <img src={image.pngUrl} alt="User avatar" width={120} height={120} />
</picture>
Enter fullscreen mode Exit fullscreen mode

Result: Images: 3-5MB → 45-80KB.

Pattern 5: Intelligent Prefetching

export function SurahNavigation({ currentSurah }) {
  const router = useRouter();
  const handlePrefetch = () => router.prefetch(`/surah/${currentSurah + 1}`);
  return (
    <Link href={`/surah/${currentSurah + 1}`} onMouseEnter={handlePrefetch} onTouchStart={handlePrefetch}>
      Next Surah
    </Link>
  );
}
Enter fullscreen mode Exit fullscreen mode

Result: Users feel instant navigation. Pages load in <200ms instead of 1-2s.

Before/After

Metric Before After Improvement
First Contentful Paint 3.2s 1.8s 44%
Largest Contentful Paint 4.8s 1.9s 60%
Time to Interactive 6.2s 2.4s 61%
Page Size 2.8MB 1.1MB 61%
Lighthouse Score 42 86 +44 pts

Performance work isn't about micro-optimizations. It's about architectural decisions that compound.

Find me at zunain.com if you're optimizing at scale.

Top comments (0)