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;
}
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
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' };
}
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 });
}
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>
);
}
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 });
}
<picture>
<source srcSet={image.webpUrl} type="image/webp" />
<img src={image.pngUrl} alt="User avatar" width={120} height={120} />
</picture>
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>
);
}
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)