웹 성능 최적화와 사용자 경험 개선
작은 개선이 큰 차이를 만드는 실전 최적화 기법
성능이 중요한 이유
사용자는 빠른 웹사이트를 선호합니다. Google의 연구에 따르면 페이지 로딩이 1초 지연될 때마다 전환율이 7% 감소합니다. 특히 심리 테스트와 같이 즉각적인 반응이 필요한 서비스에서는 성능이 더욱 중요합니다.
- 첫 인상: 3초 이내에 로딩되지 않으면 53%가 이탈합니다
- SEO: Google은 페이지 속도를 랭킹 요소로 사용합니다
- 사용자 만족도: 빠른 웹사이트는 신뢰감을 줍니다
- 모바일: 데이터 요금과 배터리 소모를 줄여줍니다
1. JavaScript 최적화
모듈 지연 로딩
모든 JavaScript를 한 번에 로드할 필요는 없습니다. 필요할 때만 로드하는 동적 import를 사용했습니다.
// 초기 로딩 시에는 필수 모듈만
import { TestController } from './controllers/TestController.js';
// 모달이 실제로 필요할 때 로드
async function showModal() {
const { ModalView } = await import('./views/ModalView.js');
const modal = new ModalView('modal-container');
modal.show();
}
이벤트 위임
각 카드마다 이벤트 리스너를 추가하는 대신, 부모 요소에 하나만 추가했습니다.
// ❌ 비효율적: 카드마다 리스너 추가
cards.forEach(card => {
card.addEventListener('click', handleClick);
});
// ✅ 효율적: 부모에 하나의 리스너
container.addEventListener('click', (e) => {
const card = e.target.closest('.test-card');
if (card) handleClick(card);
});
디바운싱과 쓰로틀링
검색 기능 등에서 불필요한 함수 호출을 줄입니다.
// 디바운싱: 마지막 호출 후 n밀리초 대기
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 사용 예시
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce((e) => {
performSearch(e.target.value);
}, 300));
2. CSS 애니메이션 최적화
transform과 opacity만 사용
GPU 가속을 받을 수 있는 속성만 애니메이션합니다.
/* ❌ 성능이 안 좋은 애니메이션 */
.card {
transition: top 0.3s, left 0.3s, width 0.3s;
}
.card:hover {
top: -10px;
width: 110%;
}
/* ✅ 성능이 좋은 애니메이션 */
.card {
transition: transform 0.3s, opacity 0.3s;
}
.card:hover {
transform: translateY(-10px) scale(1.05);
}
will-change 속성
브라우저에게 어떤 속성이 변경될지 미리 알려줍니다.
.test-card {
will-change: transform;
transition: transform 0.3s;
}
.test-card:hover {
transform: translateY(-8px);
}
주의: will-change는 실제로 애니메이션이 일어나는 요소에만 사용해야 합니다. 남용하면 오히려 성능이 떨어집니다.
3. 이미지 최적화
이모지 사용
아이콘 대신 이모지를 사용하여 HTTP 요청을 줄였습니다. 이모지는 텍스트이므로 별도의 다운로드가 필요 없습니다.
<span class="text-6xl">🧬</span> <!-- 이미지 요청 없음 -->
SVG 인라인 사용
작은 아이콘은 SVG를 HTML에 직접 넣어 HTTP 요청을 줄였습니다.
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M..."></path>
</svg>
4. 렌더링 성능 개선
가상 스크롤링 (향후 적용)
현재는 8개의 카드만 있어 문제없지만, 수백 개가 되면 가상 스크롤링이 필요합니다.
// 화면에 보이는 항목만 렌더링
class VirtualList {
constructor(items, containerHeight, itemHeight) {
this.items = items;
this.containerHeight = containerHeight;
this.itemHeight = itemHeight;
}
getVisibleItems(scrollTop) {
const start = Math.floor(scrollTop / this.itemHeight);
const end = start + Math.ceil(this.containerHeight / this.itemHeight);
return this.items.slice(start, end);
}
}
DocumentFragment 사용
여러 요소를 한 번에 DOM에 추가하여 리플로우를 줄입니다.
// ❌ 비효율적: 여러 번 DOM 조작
tests.forEach(test => {
const card = createCard(test);
container.appendChild(card); // 리플로우 발생
});
// ✅ 효율적: 한 번에 DOM 조작
const fragment = document.createDocumentFragment();
tests.forEach(test => {
const card = createCard(test);
fragment.appendChild(card);
});
container.appendChild(fragment); // 리플로우 1회만
5. 네트워크 최적화
CDN 활용
Tailwind CSS를 CDN에서 로드하여 사용자와 가까운 서버에서 빠르게 제공합니다.
<script src="https://cdn.tailwindcss.com"></script>
리소스 힌트
브라우저에게 어떤 리소스가 중요한지 알려줍니다.
<head>
<!-- DNS 미리 조회 -->
<link rel="dns-prefetch" href="https://cdn.tailwindcss.com">
<!-- 중요한 리소스 미리 로드 -->
<link rel="preload" href="css/styles.css" as="style">
<link rel="preload" href="js/app.js" as="script">
<!-- 다음 페이지 미리 연결 -->
<link rel="preconnect" href="https://api.example.com">
</head>
6. 메모리 관리
이벤트 리스너 정리
모달을 닫을 때 이벤트 리스너를 제거하여 메모리 누수를 방지합니다.
class ModalView {
constructor() {
this.handleEscKey = this.handleEscKey.bind(this);
}
showModal() {
document.addEventListener('keydown', this.handleEscKey);
}
closeModal() {
// 이벤트 리스너 제거
document.removeEventListener('keydown', this.handleEscKey);
}
}
약한 참조 사용
캐시를 구현할 때 WeakMap을 사용하면 메모리를 효율적으로 관리할 수 있습니다.
const cache = new WeakMap();
function getTestData(test) {
if (cache.has(test)) {
return cache.get(test);
}
const data = processTest(test);
cache.set(test, data);
return data;
}
7. 측정과 모니터링
Performance API 사용
실제 성능을 측정하여 개선 효과를 확인합니다.
// 페이지 로딩 시간 측정
window.addEventListener('load', () => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log('DOM 로딩:', perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart);
console.log('전체 로딩:', perfData.loadEventEnd - perfData.loadEventStart);
});
// 특정 작업 시간 측정
performance.mark('render-start');
renderCards();
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');
const measure = performance.getEntriesByName('render')[0];
console.log('렌더링 시간:', measure.duration);
Lighthouse 점수
Chrome DevTools의 Lighthouse를 사용하여 종합적인 성능을 평가합니다.
- Performance: 95점 이상 목표
- Accessibility: 접근성 개선
- Best Practices: 웹 표준 준수
- SEO: 검색 엔진 최적화
실제 적용 결과
이러한 최적화를 적용한 결과:
- 초기 로딩 시간: 1.2초 → 0.8초 (33% 개선)
- First Contentful Paint: 0.9초 → 0.5초
- Time to Interactive: 1.5초 → 1.0초
- JavaScript 번들 크기: 없음 (모듈 사용)
- 메모리 사용량: 안정적 유지
"성능 최적화는 일회성 작업이 아닌 지속적인 개선 과정입니다."
💡 핵심 요약
- 필요한 코드만 로드하고 나머지는 지연 로딩
- CSS 애니메이션은 transform과 opacity만 사용
- 이벤트 리스너는 정리하여 메모리 누수 방지
- Performance API로 실제 성능 측정
- 작은 개선의 누적이 큰 차이를 만듭니다