1. 네트워크 병목이란 무엇인가
비동기 로직이 아무리 빠르더라도,
네트워크 요청이 느리면 사용자는 “느린 사이트”라고 느낀다.
🧠 핵심 진실
- 네트워크 속도는 브라우저 밖(인터넷)에서 결정된다.
- 하지만 “언제 요청하느냐”와 “무엇을 캐싱하느냐”는 개발자가 결정할 수 있다.
2. 네트워크 요청의 흐름
브라우저가 서버에 데이터를 요청할 때 거치는 과정 👇
1️⃣ DNS 조회 → IP 주소 찾기
2️⃣ TCP 연결 (3-way handshake)
3️⃣ HTTPS 암호화 협상 (TLS)
4️⃣ 요청(Request) 전송
5️⃣ 서버 응답(Response) 수신
6️⃣ 브라우저 캐싱 및 파싱
✅ 즉, JS 코드의 fetch() 한 줄 뒤에는 수십 ms의 지연이 숨어 있다.
이걸 줄이는 게 “네트워크 최적화”의 핵심이다.
3. Fetch API 기본 구조
fetch("https://api.example.com/data")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
✅ fetch는 비동기적으로 네트워크 요청을 보낸다.
✅ Promise 기반이므로 async/await와 함께 쓰면 가독성이 높다.
async function getData() {
try {
const res = await fetch("/api/data");
const json = await res.json();
console.log(json);
} catch (err) {
console.error(err);
}
}
4. 네트워크 병목의 주요 원인
원인 설명
| 리소스 크기 과다 | 이미지, JS, CSS 파일이 크거나 압축 안 됨 |
| 중복 요청 | 동일한 데이터를 여러 컴포넌트에서 반복 호출 |
| 비효율적 캐싱 정책 | 매번 새 요청을 보냄 |
| 렌더링 차단 | CSS, JS를 동기 로드함 |
5. 캐싱(Caching) 전략
캐시는 브라우저의 “기억력”이다.
한 번 받은 데이터를 저장해두고 다시 요청할 필요를 없앤다.
(1) HTTP 캐시
HTTP 헤더를 통해 캐싱 동작을 제어할 수 있다.
헤더 설명
| Cache-Control: max-age=3600 | 1시간 동안 캐시 유지 |
| ETag | 파일 변경 감지용 태그 |
| Last-Modified | 마지막 수정 시간 |
| immutable | 절대 변경되지 않음 |
✅ 브라우저는 ETag나 수정 시간을 비교해 변경되지 않으면
서버에 요청조차 하지 않는다.
(2) Cache Storage API
PWA(Progressive Web App)나 Service Worker에서 사용.
// 캐시 저장
const cache = await caches.open("my-cache");
await cache.put("/data", new Response(JSON.stringify({ a: 1 })));
// 캐시 불러오기
const res = await cache.match("/data");
const data = await res.json();
✅ 오프라인에서도 데이터 사용 가능
✅ API 응답, 이미지, 정적 자원 모두 캐싱 가능
6. Preload / Prefetch / Preconnect
이 세 가지는 “브라우저가 리소스를 미리 받아놓도록” 하는 메커니즘이다.
태그 시점 용도
| <link rel="preload"> | 현재 페이지에서 곧 쓸 리소스 | 즉시 로딩 |
| <link rel="prefetch"> | 다음 페이지에서 쓸 리소스 | 백그라운드 로딩 |
| <link rel="preconnect"> | DNS/TLS 미리 연결 | 네트워크 준비 |
예시 👇
✅ 사용자가 클릭하기도 전에 데이터를 받아두기 때문에
“페이지 이동이 즉시 일어나는 듯한” UX를 만들 수 있다.
7. Lazy Loading — 불필요한 리소스 지연 로드
모든 이미지를 한 번에 불러오면 렌더링이 느려진다.
그럴 땐 지연 로딩(lazy loading) 을 활용한다.
<img src="thumbnail.jpg" loading="lazy" alt="이미지 설명">
✅ 사용자가 실제로 스크롤해서 볼 때만 이미지 로드
✅ 페이지 첫 렌더링 시 네트워크 부하 최소화
8. 데이터 캐싱 전략 (프론트엔드에서의 fetch)
(1) SWR 패턴 (Stale-While-Revalidate)
React나 Next.js에서 자주 쓰이는 패턴이다.
“화면은 캐시 데이터를 즉시 보여주고, 백그라운드에서 최신 데이터를 요청한다.”
const { data, error } = useSWR("/api/user", fetcher);
✅ 즉시 반응 (UX 향상)
✅ 백엔드 부하 감소
✅ SEO와 SSR에도 안전
(2) React Query 예시
const { data } = useQuery(["posts"], fetchPosts, {
staleTime: 5 * 60 * 1000, // 5분 캐싱
});
✅ 동일한 요청은 5분 내에 다시 네트워크 요청하지 않음
✅ 개발자가 캐시 만료 정책을 직접 제어 가능
9. 네트워크 지연 최소화 실무 팁
전략 설명
| gzip / brotli 압축 | 응답 크기를 최대 80% 절감 |
| CDN(Content Delivery Network) | 사용자 근처 서버에서 응답 |
| HTTP/2 병렬 요청 | 한 커넥션에서 여러 리소스 동시 전송 |
| Critical CSS | Above-the-fold 영역만 우선 로드 |
| JS 코드 스플리팅 | 필요한 모듈만 로드 (React.lazy) |
10. 실전 예시 — 빠른 첫 페이지 로딩 만들기
✅ 폰트 서버 미리 연결
✅ JS 미리 다운로드하되 DOM 파싱 차단 X (defer)
✅ 초기 렌더링에 필요한 CSS만 즉시 적용
결과적으로 LCP(Largest Contentful Paint) 개선에 직결된다.
11. 캐시 무효화(Cache Busting)
문제가 하나 있다 — 캐시된 파일이 갱신되지 않으면?
✅ 해결: 파일명에 해시값 추가
main.3f92a.js
main.4b7c1.js
또는
<script src="/main.js?v=4b7c1"></script>
➡ 빌드 시점마다 버전이 달라져 캐시 충돌을 방지한다.
12. 프리페치(Next.js 예시)
Next.js에서는 <Link> 태그만 써도 자동으로 prefetch가 된다.
<Link href="/about" prefetch={true}>About</Link>
✅ 사용자가 마우스를 올리는 순간 다음 페이지 리소스를 미리 받아둠
✅ 라우팅이 거의 0.1초 수준으로 단축
13. 성능 분석 도구
도구 기능
| Chrome DevTools – Network 탭 | 요청 시간, 캐시 여부, 리소스 크기 분석 |
| Lighthouse | Core Web Vitals 자동 점검 |
| WebPageTest.org | TTFB, DNS Lookup 시간 시각화 |
| Bundle Analyzer | JS 번들 크기 분석 |
14. 마무리
“빠른 프론트엔드”는 코드 속도가 아니라
네트워크와 브라우저의 흐름을 미리 읽는 개발자의 감각이다.
Preload, Cache, Prefetch, Lazy Loading은 단순 옵션이 아니라,
사용자의 ‘기다림’을 없애는 심리적 기술이다.
“속도가 아니라 타이밍을 제어하는 것이 진짜 최적화다.”
'frontend > javascript' 카테고리의 다른 글
| 🟨 2-30. 웹 성능 모니터링과 자동 분석 — Lighthouse CI, Web Vitals, Sentry 통합 가이드 (0) | 2025.11.07 |
|---|---|
| 🟨 2-29. Core Web Vitals 완전 해부 — LCP, CLS, FID로 웹 성능 측정하기 (0) | 2025.11.07 |
| 🟨 2-27. 비동기 코드의 병목 해결 — Event Loop, Promise, Web API의 완전한 협력 구조 (0) | 2025.11.07 |
| 🟨 2-26. 메모리 누수와 성능 저하를 막는 실전 관리 전략 (JS & 브라우저 관점) (0) | 2025.11.07 |
| 🟨 2-25. 자바스크립트 엔진 내부 구조 — V8 엔진이 코드를 처리하는 진짜 방식 (0) | 2025.11.07 |