Import on Visibility: Lazy Loading with IntersectionObserver
Import on Visibility combines IntersectionObserver with dynamic import() to load JavaScript-heavy components only when they approach the user's viewport. This pattern further reduces initial bundle size for below-the-fold content while providing smooth perceived performance through rootMargin prefetching.
Static imports = cooking everything before opening the restaurant. Import on Interaction = cooking after they order. Import on Visibility = starting prep when you see them walking toward the table (via IntersectionObserver). You save resources on startup and still deliver smoothly when they arrive.
1IntersectionObserver Basics
The modern, efficient way to detect when an element enters the viewport without expensive scroll listeners. Use rootMargin to start loading slightly before the element is visible.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadHeavyComponent();
observer.disconnect();
}
});
}, { rootMargin: '150px' });
observer.observe(placeholderElement);2Basic Implementation
Combine observer with dynamic import() to load and mount the real component only when needed.
3React Hook Pattern
Encapsulate logic into a reusable hook for clean, production-ready usage with proper cleanup and state management.
function useLoadOnVisibility(loader, rootMargin = '150px') {
const ref = useRef(null);
const [Component, setComponent] = useState(null);
// ... loading & error states
}4Reusable Component & Advanced Tips
Create <LazyLoadOnVisible /> wrapper. Support parallel imports, meaningful placeholders, error handling, and cleanup to prevent memory leaks.
| Property | Import on Mount (useEffect) | Import on Interaction | Import on Visibility |
|---|---|---|---|
| Behavior | Loads as soon as component mounts | Loads on click/hover/focus | Loads when near viewport |
| Use Case | Content needed soon after render | User-triggered features (modals, editors) | Below-the-fold heavy widgets |
| Performance | Earlier but less optimal | Best for explicit intent | Balances delay with smoothness |
| Bundle Impact | Still affects initial load if above fold | Very small initial bundle | Small initial bundle |
Import on Mount (useEffect)
Behavior
Loads as soon as component mounts
Use Case
Content needed soon after render
Performance
Earlier but less optimal
Bundle Impact
Still affects initial load if above fold
Import on Interaction
Behavior
Loads on click/hover/focus
Use Case
User-triggered features (modals, editors)
Performance
Best for explicit intent
Bundle Impact
Very small initial bundle
Import on Visibility
Behavior
Loads when near viewport
Use Case
Below-the-fold heavy widgets
Performance
Balances delay with smoothness
Bundle Impact
Small initial bundle
Common questions
- ›“How would you lazy load a component only when it scrolls into view?”
- ›“Compare import on interaction vs import on visibility.”
- ›“How do you implement this pattern in React?”
- ›“What are the trade-offs and best practices?”
What interviewers look for
- Correct use of IntersectionObserver + dynamic import()
- Understanding of rootMargin for prefetching
- Proper cleanup and single-load guarantees
- Awareness of good vs bad candidates and UX considerations
Short answer (60 sec)
Use IntersectionObserver to watch a placeholder. When it enters the viewport (with rootMargin), trigger dynamic import() to load the heavy component. This keeps the initial bundle small while loading below-the-fold content just in time.
Detailed answer (senior level)
This pattern is ideal for comments, embeds, charts, and other below-the-fold heavy UI. rootMargin (e.g. 150px) starts loading early for smoother experience. Always disconnect the observer after loading, cache the module, and provide meaningful placeholders + error states. It complements import on interaction and React.lazy for comprehensive lazy loading strategy.
- Forgetting to disconnect the observer (memory leaks)
- Using scroll listeners instead of IntersectionObserver
- Loading critical/above-the-fold content this way
- No loading states or poor placeholders
- Not handling errors or multiple triggers
- ✓IntersectionObserver + dynamic import() = efficient below-the-fold lazy loading
- ✓Use positive rootMargin to prefetch before visibility
- ✓Always disconnect observer and guard against duplicate loads
- ✓Best for comments, embeds, charts, recommendations — not core UI
- ✓Provide excellent placeholders and error handling for great UX
- ✓Combine with import on interaction for complete lazy loading coverage