지연 로딩과 비동기 로딩 (Lazy Loading and Async Loading)
자바스크립트에서 성능을 높이고 초기 로딩을 빠르게 하려면 지연 로딩과 비동기 로딩이 유용하다. 필요할 때만 리소스를 불러오면 사용자 경험이 더 부드러워진다. 이번에는 이 두 가지 방법을 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.
이 방법을 잘 다루면 페이지 로드가 빨라지고 리소스 낭비도 줄일 수 있다. 하나씩 살펴보며 어떤 상황에서 어떻게 적용할지 알아보자.
지연 로딩 기본
지연 로딩은 필요할 때까지 리소스 로드를 미루는 방식이다. 이미지 로딩을 예로 들어보자:
<!-- HTML -->
<img data-src="image.jpg" class="lazy" alt="지연 로딩 이미지">
// JavaScript
function lazyLoadImages() {
const images = document.querySelectorAll(".lazy");
images.forEach(img => {
if (img.getBoundingClientRect().top < window.innerHeight) {
img.src = img.dataset.src;
img.classList.remove("lazy");
}
});
}
window.addEventListener("scroll", lazyLoadImages);
lazyLoadImages();
화면에 보일 때만 이미지를 로드하니 초기 로딩 속도가 빨라졌다.
1. Intersection Observer로 지연 로딩
IntersectionObserver
를 사용하면 더 효율적으로 관리할 수 있다:
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
obs.unobserve(img);
}
});
});
const images = document.querySelectorAll(".lazy");
images.forEach(img => observer.observe(img));
스크롤 이벤트를 직접 듣는 대신 브라우저가 교차 여부를 감지하니 성능 부담이 줄었다.
2. 모듈 비동기 로딩
필요한 모듈을 동적으로 불러오면 초기 부담이 줄어든다:
async function loadModule() {
const module = await import("./heavyModule.js");
console.log(module.doSomething());
}
const button = document.querySelector("#loadButton");
button.addEventListener("click", loadModule);
사용자가 버튼을 누를 때만 모듈을 로드하니 페이지 시작이 빨라졌다.
3. 스크립트 비동기 로딩
async
와 defer
로 외부 스크립트를 효율적으로 불러오자:
<!-- HTML -->
<script src="script.js" async></script>
<script src="deferScript.js" defer></script>
// script.js
console.log("비동기 로딩 완료");
// deferScript.js
console.log("defer 로딩 완료");
async
는 로드가 끝나는 대로 실행되고, defer
는 DOM이 준비된 후 순서대로 실행된다. 상황에 맞게 선택하면 된다.
4. 이미지 프리로드와 지연 로딩 조합
중요한 이미지는 미리 로드하고 나머지는 지연 로딩으로 처리할 수 있다:
<!-- HTML -->
<link rel="preload" href="hero.jpg" as="image">
<img src="hero.jpg" alt="메인 이미지">
<img data-src="lazy.jpg" class="lazy" alt="지연 이미지">
// JavaScript
const lazyImages = document.querySelectorAll(".lazy");
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
}
});
});
lazyImages.forEach(img => observer.observe(img));
preload
로 핵심 이미지를 먼저 로드하고, 나머지는 지연 로딩으로 균형을 맞췄다.
5. 컴포넌트 지연 로딩
프레임워크에서 컴포넌트를 필요할 때 불러오자 (React 예시):
import React, { lazy, Suspense } from "react";
const HeavyComponent = lazy(() => import("./HeavyComponent"));
function App() {
return (
<div>
<Suspense fallback=<div>로딩 중...</div>>
<HeavyComponent />
</Suspense>
</div>
);
}
lazy
와 Suspense
로 컴포넌트를 동적으로 로드하니 초기 번들 크기가 줄었다.
6. 비동기 데이터 로딩
데이터를 비동기로 불러와서 UI를 점진적으로 채울 수 있다:
async function loadData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
const list = document.querySelector("#list");
list.innerHTML = data.map(item => `<li>${item.title}</li>`).join("");
}
window.addEventListener("load", loadData);
페이지가 먼저 렌더링되고 데이터가 나중에 채워지니 사용자 대기 시간이 줄었다.
7. 코드 스플리팅과 비동기 로딩
Webpack으로 코드 스플리팅을 적용해보자:
// app.js
function loadFeature() {
import("./feature").then(module => {
module.runFeature();
});
}
const button = document.querySelector("#featureButton");
button.addEventListener("click", loadFeature);
// webpack.config.js
module.exports = {
entry: "./app.js",
output: {
filename: "[name].[contenthash].js",
chunkFilename: "[name].[contenthash].chunk.js"
}
};
동적 임포트로 필요한 코드만 분리해서 로드하니 초기 번들이 가벼워졌다.
8. 비디오 지연 로딩
비디오도 필요할 때만 로드할 수 있다:
<!-- HTML -->
<video data-src="video.mp4" class="lazy-video" controls></video>
// JavaScript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target;
video.src = video.dataset.src;
observer.unobserve(video);
}
});
});
const videos = document.querySelectorAll(".lazy-video");
videos.forEach(video => observer.observe(video));
비디오가 뷰포트에 들어올 때만 로드하니 대역폭이 절약됐다.
9. 조건부 비동기 로딩
특정 조건에서만 리소스를 로드할 수 있다:
async function loadIfNeeded(condition) {
if (condition) {
const module = await import("./optionalModule.js");
module.run();
} else {
console.log("로드 필요 없음");
}
}
window.addEventListener("scroll", () => {
loadIfNeeded(window.scrollY > 500);
});
스크롤이 500px을 넘을 때만 모듈을 로드하니 불필요한 요청이 줄었다.
10. 서비스 워커와 비동기 로딩
서비스 워커로 리소스를 비동기적으로 관리해보자:
// sw.js
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request).then(response => {
if (response) return response;
return fetch(event.request).then(res => {
const clone = res.clone();
caches.open("dynamic").then(cache =>
cache.put(event.request, clone)
);
return res;
});
})
);
});
// main.js
navigator.serviceWorker.register("/sw.js");
캐시가 없으면 비동기로 가져오고 저장하니 오프라인에서도 동작이 부드러워졌다.
마무리
지연 로딩과 비동기 로딩은 성능 최적화와 사용자 경험 개선에 큰 도움을 준다. 이미지, 모듈, 데이터, 서비스 워커까지 상황에 맞게 적용하면 더 빠르고 효율적인 애플리케이션을 만들 수 있다.
'코딩 공부 > 자바스크립트' 카테고리의 다른 글
86. 자바스크립트 렌더링 최적화 (Rendering Optimization) (2) | 2025.03.29 |
---|---|
85. 자바스크립트 메모리 관리 (Memory Management) (1) | 2025.03.28 |
83. 자바스크립트 캐싱 전략 (Caching Strategies) (2) | 2025.03.28 |
82. 자바스크립트 코드 최적화 기법 (Code Optimization Techniques) (1) | 2025.03.27 |
81. 자바스크립트 디버깅 팁 (1) | 2025.03.27 |