I once spent a week trying to fix a performance problem for an Abu Dhabi real estate client. Their Lighthouse score was 38 — worse than a 2012 WordPress site. The culprit? A 2MB JavaScript bundle dragging everything down. After tearing the whole thing apart, lazy loading components and splitting code reduced initial load from 4.2s to 1.8s. Let me show you how I've used these tools in production, without overcomplicating things.
How I Actually Use React.lazy() in Production
Don’t over-engineer it. If you’re building a page that has a non-critical component — like a booking form or a photo gallery — wrap it in React.lazy() and throw a fallback in front of it. Here’s how I did it for Tawasul Limo’s booking page:
const PaymentForm = React.lazy(() => import('../components/PaymentForm'))
// Later in the component
<Suspense fallback="Loading payment options...">
<PaymentForm />
</Suspense>This works best for interactive components that don’t block the first paint. But be careful — I once had a client who used lazy loading for the main navigation, and the flash of missing UI caused a 20% jump in bounce rate. UX matters more than a Lighthouse score.
Also, don’t assume lazy loading always reduces time to interactive. I tested one Next.js project (Next 14, React 18) and saw TTI increase by 300ms when overusing component lazy loading. Split only what’s non-critical.
My Route-Based Code Splitting Workflow
Next.js handles route-based splitting by default in the App Router. Each route gets its own bundle — but most people don’t know you can customize this by folder structure. For example, I use shared layouts for marketing pages and admin panels:
/app
/marketing
page.tsx
/dashboard
/analytics
page.tsx
/users
page.tsx When a user hits /marketing, they only download ~80KB of React code, not the 400KB analytics dashboard bundle. Saved a client in Dubai $1,200/month on CDN costs by splitting their B2B SaaS app like this. Don’t overthink it — your folder structure becomes your code splitting strategy.
Dynamic Imports for Non-Critical Features
Third-party libraries are a killer. I once had a client who shipped moment.js as part of their main bundle — 720KB of timezone data nobody asked for. Here’s what I did:
// Instead of importing at the top
import { format } from 'date-fns' // No good
// Dynamic import inside a function
async function loadDateUtils() {
const dateFns = await import('date-fns')
return dateFns.format(new Date(), 'PPP')
}Use this pattern for:
- •Analytics scripts that only run after page interaction
- •Heavy UI libraries (e.g., a code editor widget you only use in one module)
- •AI features like live translation tools (see my work on Reach Home Properties' bilingual search bar)
Images and Media: Lazy Load Everything
I’ll be real — developers still mess this up. For images, always use the Image component with loading="lazy" unless it's in the hero section. And don’t forget to optimize them. I once optimized 60 images for a UAE client’s product catalog by switching from JPEG to WebP and got their LCP down from 5.3s to 2.1s. There’s a reason I wrote a guide on image optimization for UAE websites.
For video embeds, I use this simple trick:
<video controls preload="none" width="100%">
<source src="/large-video.mp4" type="video/mp4" />
</video>Adding preload="none" and not setting a poster attribute saves 1-2MB of data. Users clicking play is not a hard requirement.
When Code Splitting Backfires
I spent three days earlier this year trying to optimize a Next.js 14 app for a Dubai logistics company. Split the heck out of everything — components, routes, you name it. Result? Their staging environment got slower. Turns out, their deployment provider (not Vercel) didn’t support HTTP/2, so all those extra chunks turned into 30+ additional HTTPS requests. Went from 8s load time to 12s. Ditched code splitting for the vendor files and used a single optimized bundle instead.
Monitoring Performance: Tools That Don’t Waste Time
I test locally using Chrome DevTools and Lighthouse, but never fully trust the numbers. Real users are on 3G in Ras Al Khaimah or using Chrome on a 4-year-old Galaxy S10. So here’s what I do:
- Enable Vercel Analytics for client sites — actual CLS, LCP, and FID metrics
- Test with
npx serve -s . -l 3000and throttle network speed to "Fast 3G" in DevTools - Add
console.time()markers in critical rendering paths
One thing that saved me hours: using the React Developer Tools component profiler to see which components were causing bottlenecks. You don't need a fancy APM setup for small projects.
Frequently Asked Questions
How do I lazy load images in Next.js without breaking SEO?
Use the built-in Image component with loading="lazy" and always specify width/height. Don't forget to add alt text — I've had Arabic SEO audits flag missing alt attributes on bilingual sites (like Tawasul Limo’s landing pages).
When should I avoid code splitting in Next.js?
Skip it for landing pages under 100KB. For example, a simple SaaS marketing page without heavy interactivity will load faster with no splits. Also, if you're deploying to non-HTTP/2 environments — splitting causes more network requests.
How do I test code splitting in a deployed environment?
Open DevTools, go to the Network tab, and look for chunk files under the "JS" filter. Click the "Initiator" column to see how each script got loaded. For real metrics, use webpagetest.org with a 3G connection and mobile viewport.
Can lazy loading affect content visibility on the page?
Yes. If you lazy load a component that affects layout (like a table that pushes text down), you’ll hit a CLS penalty. Wrap that component in a or a fixed-size container to prevent layout shifts.
If you're building a high-traffic platform in the UAE and need performance done right, get in touch — I’ve dealt with the same issues you’re facing this week. Whether you're shipping a bilingual e-commerce site or a real estate platform like Reach Home Properties, I can help you ship faster without burning out your team.