리소스 로딩 최적화란?
웹 애플리케이션이 사용하는 리소스(JavaScript, CSS, 이미지, 폰트 등)를 효율적으로 로드하는 것
Why? 웹 애플리케이션의 성능을 향상시키고, 사용자 경험을 개선하기 위해
- 페이지 로드 속도 개선 : 사용자가 빠르게 콘텐츠를 볼 수 있도록 지원
- 사용자 경험(UX) 향상 : 빠르고 원활한 사용자 인터페이스 제공
- 네트워크 대역폭 효율화 : 데이터 크기를 줄여 네트워크 트래픽을 줄일 수 있음
How?
- 이미지 최적화 : 이미지 크기 줄이기, 포맷 변경, Lazy Loading 등
- 코드 최적화 : JavaScript와 CSS 난독화 및 병합, 불필요한 코드 제거
- 리소스 로드 순서 최적화 : 중요한 리소스 우선적으로 로드하고, 나머지는 비동기로 로드
- Lazy Loading : 사용자가 필요로 할 때만 리소스를 로드
Static Import 정적 자원 로딩 패턴
애플리케이션의 정적 자원(이미지, CSS, 폰트 등)을 효율적으로 로드하기 위한 패턴
정적 자원의 URL이나 경로를 소스 코드에 명시적으로 정의 ⇒ 런타임 아닌 빌드 타임에 자원을 준비한다!
import logo from './assets/logo.png'; import './styles/global.css'; import data from './data/info.json'; function App() { return ( <img src={logo} alt="Logo" />, <h1>Hello, World!</h1>, <div>{data.title}</div> ); }
이미지, CSS, JSON 데이터를 정적으로 가져온다. 빌드 타임에 번들링하여 런타임에 동적으로 요청하지 않는다.
예제 : 이모지 사용 채팅 앱
아래와 같이 이모지를 사용할 수 있는 채팅 앱을 만든다고 가정하자. 입력창에서 이모지 버튼을 누르면 이모지창이 나타난다.
// App.js import React from "react"; // 정적 로드 import UserInfo from "./components/UserInfo"; import ChatList from "./components/ChatList"; import ChatInput from "./components/ChatInput"; import "./styles.css"; console.log("App loading", Date.now()); const App = () => ( <div className="App"> <UserInfo /> <ChatList /> <ChatInput /> </div> ); export default App;
ChatInput
으로 입력을 구현한다.// ChatInput.js import React from "react"; import Send from "./icons/Send"; import Emoji from "./icons/Emoji"; import EmojiPicker from "./EmojiPicker"; const ChatInput = () => { const [pickerOpen, togglePicker] = React.useReducer(state => !state, false); return ( <div className="chat-input-container"> <input type="text" placeholder="Type a message..." /> <Emoji onClick={togglePicker} /> {pickerOpen && <EmojiPicker />} <Send /> </div> ); }; console.log("ChatInput loaded", Date.now()); export default ChatInput;
Emoji
를 누르면 이모지창이 나타나는 EmojiPicker
가 실행된다.// EmojiPicker.js import React from "react"; import Picker from "emoji-picker-react"; const EmojiPicker = () => ( <div className="emoji-picker"> <Picker /> </div> ); export default EmojiPicker;
라이브러리를 삽입해
Picker
로 이모지를 선택해 삽입한다.모든 리소스는 정적으로 로드되었다.
이 앱의 소스코드는
main.bundle.js
라는 하나의 번들을 생성한다.위처럼 main 번들은 1.5Mib 크기를 가진다.
장점
- 정적 자원이 빌드 타임에 최적화되어서 런타임 성능 개선
- 브라우저 캐싱을 통해 재요청 없이 빠르게 제공
- 의존성이 코드 내에 명확히 정의되어 관리하기 쉽고 디버깅 용이
- 경로변경이 발생해도 유지보수 용이
단점
- 번들 크기 증가
- 빌드 시간 증가
- 유연성 감소
Dynamic Import 동적 자원 로딩 패턴
필요한 시점에만 특정 리소스(모듈, 컴포넌트, 라이브러리 등)를 로드하여 성능 최적화 ⇒ 런타임에 로드!
동작원리
- 런타임에서 리소스 로드
- import() 사용하여 JavaScript 모듈을 런타임에서 비동기적으로 로드
- 코드 스플리팅
- 빌드 도구(Webpack, Vite 등)가 동적 import 감지하고, 모듈을 별도의 번들로 분리
- 특정 시점에 이 번들을 요청하여 로드
- Lazy Loading 지연 로딩
- 사용자가 특정 경로나 기능에 접근할 때만 필요한 모듈로 로드
import React, { Suspense } from "react"; // Lazy Loading const MyComponent = React.lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> // 로딩 상태 표시 <MyComponent /> // 렌더링 시점에 로드 </Suspense> ); } export default App;
React의 Suspense와 React.lazy를 사용하여 동적 로딩을 구현한다.
예제 : 이모지 사용 채팅 앱
// ChatInput.js import React, { Suspense, lazy } from "react"; import Send from "./icons/Send"; // 정적 로드 import Emoji from "./icons/Emoji"; // 정적 로드 // EmojiPicker 동적 로드 const EmojiPicker = lazy(() => import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker") ); const ChatInput = () => { const [pickerOpen, togglePicker] = React.useReducer(state => !state, false); return ( <Suspense fallback={<p id="loading">Loading...</p>}> <div className="chat-input-container"> <input type="text" placeholder="Type a message..." /> <Emoji onClick={togglePicker} /> {pickerOpen && <EmojiPicker />} <Send /> </div> </Suspense> ); }; console.log("ChatInput loaded", Date.now()); export default ChatInput;
동적으로
EmojiPicker
를 import 했다. Emoji
를 누르기 전까지 EmojiPicker
를 불러오지 않는다. 이모지 버튼을 클릭 시, EmojiPicker
가 랜더링되며 번들링한다.이 앱은 총
main.bundle.js
와 emoji-picker.bundle.js
두 개의 번들이 생성된다.영상처럼 앱이 시작될 때 main 번들이 생성되고, 이모지 버튼을 클릭할 때 emoji-picker 번들이 생성된다.
main 번들의 크기가 1.5 MiB에서 1.33 MiB로 줄었다.
이모지를 쓰려면 사용자는 조금의 시간을 더 기다려야 하지만, 앱을 더 빨리 보고 인터랙션 할 수 있다.
장점
- 초기 로드 속도 최적화
- 코드 스플리팅
- 사용자 경험 UX 향상
- 리소스 효율성
단점
- 동적 로드 시 지연 가능성
- 코드 복잡도 증가
- SEO 문제
- 동적로드된 콘텐츠는 검색 엔진이 크롤링하지 못할 수 있음 ⇒ SSR 또는 프리렌더링을 결합해야 함
When?
- 대규모 애플리케이션 : 여러 페이지와 기능이 있는 애플리케이션에서, 페이지별로 코드 분리하여 로드
- 사용 빈도가 낮은 기능
- 제3자 라이브러리 : 무거운 라이브러리
- 동적 콘텐츠
Static Import vs Dynamic Import
특징 | Static Import | Dynamic Import |
로드 시점 | 빌드 타임 | 런타임 |
번들 크기 | 하나의 번들로 병합 | 모듈별로 번들 분리 |
초기 로드 시간 | 느림 | 빠름 |
유연성 | 조건부 로드 불가능 | 조건부 로드 가능 |
SEO 적합성 | 좋음 | 나쁨(SSR로 보완 가능) |