최적화 2026.02.13 📖 13분 분량

웹 성능 최적화와 사용자 경험 개선

작은 개선이 큰 차이를 만드는 실전 최적화 기법

성능이 중요한 이유

사용자는 빠른 웹사이트를 선호합니다. 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로 실제 성능 측정
  • 작은 개선의 누적이 큰 차이를 만듭니다