- CLAUDE.md 운영 규칙 - wiki/ 정리된 지식 페이지 (Nuxt + Claude Code) - raw/ 원본 자료 - reference/ Nuxt 4.x 공식 문서 Co-authored-by: Cursor <cursoragent@cursor.com>
6.3 KiB
6.3 KiB
Nuxt 성능 최적화 & 하이드레이션
카테고리: 성능 & 배포 최종 수정: 2026-05-13 관련: nuxt-rendering-modes, nuxt-lifecycle, nuxt-data-fetching
요약
Nuxt 성능 최적화는 내장 기능(Lazy 컴포넌트, 스마트 프리패치, 데이터 캐싱)과 공식 모듈(Image, Fonts, Scripts), 그리고 하이드레이션 오류 방지 세 가지 축으로 나뉜다.
내장 성능 기능
스마트 프리패치 (<NuxtLink>)
<NuxtLink>가 뷰포트에 들어오면 자동으로 다음 페이지의 JS와 payload 프리패치.
<NuxtLink to="/about">소개</NuxtLink>
뷰포트 대신 인터랙션 시 프리패치로 변경:
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
prefetchOn: { interaction: true, visibility: false },
},
},
},
})
Lazy 컴포넌트 (지연 로딩)
Lazy 접두사로 컴포넌트 지연 로딩. 번들 크기 최소화.
<template>
<div>
<LazyMountainsList v-if="show" />
<button v-else @click="show = true">목록 보기</button>
</div>
</template>
Lazy Hydration (Nuxt v3.16+)
초기 로드 시 모든 컴포넌트를 인터랙티브하게 만들지 않아도 됨.
<template>
<LazyMyComponent hydrate-on-visible /> <!-- 뷰포트 진입 시 -->
<LazyHeavyChart hydrate-on-idle /> <!-- 브라우저 유휴 시 -->
<LazyModal hydrate-on-interaction="click" /> <!-- 인터랙션 시 -->
</template>
데이터 이중 패칭 방지
useFetch / useAsyncData는 서버에서 가져온 데이터를 payload로 전달 → 클라이언트에서 재요청 없이 재사용.
Hybrid Rendering으로 성능 극대화
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // 빌드 시 생성
'/products/**': { swr: 3600 }, // 1시간 캐시 + 백그라운드 재생성
'/blog': { isr: 3600 }, // CDN 캐시 1시간
'/admin/**': { ssr: false }, // 클라이언트 전용
},
})
공식 모듈
Nuxt Image
이미지 자동 최적화, WebP/Avif 변환, lazy loading.
<template>
<!-- LCP 이미지: eager + high priority -->
<NuxtImg
src="/hero-banner.jpg"
format="webp"
:preload="{ fetchPriority: 'high' }"
loading="eager"
width="200"
height="100"
/>
<!-- 일반 이미지: lazy loading -->
<NuxtImg
src="/thumbnail.jpg"
format="webp"
loading="lazy"
fetchpriority="low"
width="100"
height="100"
/>
</template>
Nuxt Fonts
폰트 자동 self-hosting, CLS 감소, 캐시 헤더 설정.
CSS font-family 선언만 있으면 나머지 자동 처리.
Nuxt Scripts
서드파티 스크립트(GA, 지도, SNS) 성능 최적화.
const { proxy } = useScriptGoogleAnalytics({
id: 'G-1234567',
scriptOptions: { trigger: 'manual' },
})
proxy.gtag('config', 'UA-123456789-1')
하이드레이션 오류
하이드레이션 오류 = 서버에서 렌더링한 HTML과 클라이언트에서 Vue가 생성하는 DOM이 불일치. 단순 경고가 아님 — 인터랙티비티 지연, 이벤트 리스너 미연결, 상태 불일치 야기.
주요 원인과 해결법
1. 브라우저 전용 API를 서버에서 사용
<!-- ❌ localStorage는 서버에서 없음 -->
<script setup>
const theme = localStorage.getItem('theme') || 'light'
</script>
<!-- ✅ useCookie 사용 -->
<script setup>
const theme = useCookie('theme', { default: () => 'light' })
</script>
2. 서버·클라이언트 데이터 불일치
<!-- ❌ 서버와 클라이언트에서 다른 값 -->
<template><div>{{ Math.random() }}</div></template>
<!-- ✅ useState로 일관된 값 유지 -->
<script setup>
const state = useState('random', () => Math.random())
</script>
<template><div>{{ state }}</div></template>
3. 시간 기반 조건 렌더링
<!-- ❌ 서버와 클라이언트의 시각이 다름 -->
<script setup>
const hour = new Date().getHours()
const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'
</script>
<!-- ✅ NuxtTime 컴포넌트 또는 onMounted에서 처리 -->
<script setup>
const greeting = ref('Hello!')
onMounted(() => {
const hour = new Date().getHours()
greeting.value = hour < 12 ? '좋은 아침입니다' : '안녕하세요'
})
</script>
4. 서드파티 라이브러리 DOM 조작
<!-- ❌ 서버에서 import 시 에러 -->
<script setup>
if (import.meta.client) {
const { default: SomeLib } = await import('browser-only-lib')
SomeLib.init()
}
</script>
<!-- ✅ onMounted에서 초기화 -->
<script setup>
onMounted(async () => {
const { default: SomeLib } = await import('browser-only-lib')
SomeLib.init()
})
</script>
5. ClientOnly로 클라이언트 전용 콘텐츠 격리
<template>
<ClientOnly>
<BrowserOnlyComponent />
<template #fallback>
<p>로딩 중...</p>
</template>
</ClientOnly>
</template>
하이드레이션 오류 체크리스트
localStorage,window,document→onMounted또는import.meta.client로 분기Math.random(),Date.now()→useState로 서버 값 유지window.innerWidth조건 렌더링 → CSS 미디어 쿼리로 대체- 서드파티 라이브러리 초기화 →
onMounted안으로 이동 - SSR 중 사이드 이펙트 →
<script setup>루트가 아닌onMounted내부로
성능 측정 도구
| 도구 | 용도 |
|---|---|
nuxi analyze |
번들 시각화 (vite-bundle-visualizer) |
| Nuxt DevTools | Timeline, Render Tree, Inspect |
| Chrome DevTools > Performance | LCP, CLS, INP |
| Chrome Lighthouse | 종합 성능·접근성·SEO 감사 |
| PageSpeed Insights | 실사용자 필드 데이터 포함 |
흔한 성능 실수
| 문제 | 해결 |
|---|---|
| 플러그인 과다 사용 | composable 또는 유틸 함수로 대체 |
| 미사용 의존성 | package.json 정기 점검, 번들 분석 |
| Vue 최적화 미적용 | shallowRef, v-memo, v-once 활용 |
| 모든 것을 동시 로딩 | Progressive Enhancement, Lazy 컴포넌트 |
참고 / 출처
reference/3.guide/2.best-practices/performance.mdreference/3.guide/2.best-practices/hydration.md