A key page had:
- Many client-only components (maps, chat widgets, dynamic UI)
- Heavy third-party scripts
- Large JS execution time (~2s+)
- Slow initial render and delayed interactivity Even though everything worked, the page felt heavy and slow.
What I Actually Changed?
1. Lazy-loaded non-critical components (below the fold)
Before:
All components were imported normally ā loaded in the initial bundle
After:
Used dynamic imports to defer non-critical UI
const HeavyComponent = defineAsyncComponent(() =>
import('~/components/HeavyComponent.vue')
)
For React/Next:
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
ssr: false,
})
š Result: Reduced initial JS bundle and faster first paint
2. Delayed rendering using visibility (not just lazy import)
Some components were still loading too early.
Fix:
Only render when visible in viewport
const showComponent = ref(false)
onMounted(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
showComponent.value = true
observer.disconnect()
}
})
observer.observe(target.value)
})
š Result: Components load only when user scrolls
3. Isolated client-only logic (SSR-safe fixes)
Problem:
Components using window, maps, etc. were causing unnecessary overhead and warnings.
Fix:
Wrapped them in client-only rendering
<ClientOnly>
<MapComponent />
</ClientOnly>
or in React:
if (typeof window === 'undefined') return null
š Result: Cleaner hydration + no unnecessary SSR work
4. Reduced third-party impact (biggest win)
Problem:
Map libraries + external scripts blocking main thread
Fix:
Load scripts only after user interaction or visibility
Removed auto-loading behavior
Example:
onMounted(() => {
setTimeout(() => {
loadMapScript()
}, 2000)
})
š Result: Reduced main thread blocking + Improved Time to Interactive
5. Fixed unnecessary re-renders
Problem:
Watchers and reactive state triggering extra updates
Fix:
Removed redundant watchers
Moved logic to computed where possible
Before:
watch(data, () => {
processData()
})
After:
const processed = computed(() => processData(data.value))
š Result: Less JS execution, smoother UI
6. Optimized images (real impact)
Problem:
Large images loading at full size
Fix:
Used resized + compressed images (via CDN params)
Ensured correct aspect ratios
š Result: Faster LCP, Lower network payload and Final Result
After these changes:
- Noticeably faster page load
- Reduced JS execution time
- Better Lighthouse performance
- Smoother UX (especially on slower devices)
š Performance issues in real apps are rarely about one big fix.
They come from:
- Loading too much too early
- Not controlling when components render
- Letting third-party scripts block everything
š Fixing those made the biggest difference.
Iām currently open to frontend roles (React / Next.js / Vue), especially where performance and real-world UI challenges matter.
If you're hiring or working on something interesting, feel free to reach out.
Top comments (0)