Files
nuxt-deep/docs/curriculum/02-rendering-lifecycle.md

165 lines
3.9 KiB
Markdown

# 2. Rendering 흐름 & Lifecycle
## SSR → Hydration → CSR 전환 흐름
Nuxt SSR은 세 단계로 구성된다.
```
[브라우저 요청]
[서버] setup() 실행 → HTML 생성 → 클라이언트로 전송
[브라우저] HTML 즉시 표시 (사용자에게 보임)
[브라우저] Vue JS 로드 → Hydration (서버 HTML에 이벤트 연결)
[브라우저] onMounted() 실행 → 완전한 SPA처럼 동작
```
---
## Lifecycle 훅 실행 위치
| 훅 | 서버 | 클라이언트 | 설명 |
|----|------|------------|------|
| `setup()` | ✅ | ✅ | 컴포넌트 초기화, 양쪽 모두 실행 |
| `onServerPrefetch()` | ✅ | ❌ | 서버에서만 실행, SSR용 데이터 페칭 |
| `onBeforeMount()` | ❌ | ✅ | DOM 마운트 직전 |
| `onMounted()` | ❌ | ✅ | DOM 마운트 완료, 브라우저 API 사용 가능 |
| `onBeforeUnmount()` | ❌ | ✅ | 컴포넌트 제거 직전 |
| `onUnmounted()` | ❌ | ✅ | 컴포넌트 제거 완료 |
---
## 실전 코드 예시
### setup()에서의 서버/클라이언트 분기
```vue
<script setup>
// setup()은 서버와 클라이언트 양쪽에서 실행됨
const config = useRuntimeConfig()
// 서버/클라이언트 환경 확인
if (import.meta.server) {
console.log('서버에서만 출력')
}
if (import.meta.client) {
console.log('클라이언트에서만 출력')
}
</script>
```
### onServerPrefetch — 서버에서 데이터 미리 준비
```vue
<script setup>
const data = ref(null)
// 서버에서 먼저 데이터를 가져오고, 클라이언트로 상태 전달
onServerPrefetch(async () => {
data.value = await $fetch('/api/products')
})
</script>
```
> `useAsyncData` / `useFetch`가 내부적으로 `onServerPrefetch`를 사용한다.
> 직접 쓸 일은 드물지만, 동작 원리 이해에 중요하다.
### onMounted — 브라우저 전용 작업
```vue
<script setup>
// ❌ 잘못된 예: setup()에서 window 접근 → 서버에서 오류 발생
// const width = window.innerWidth
// ✅ 올바른 예: onMounted에서 브라우저 API 사용
const windowWidth = ref(0)
onMounted(() => {
windowWidth.value = window.innerWidth
// 이벤트 리스너, setTimeout, localStorage 등 모두 여기서
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 반드시 정리해야 메모리 누수를 막는다
window.removeEventListener('resize', handleResize)
})
</script>
```
---
## Hydration이란?
서버가 생성한 정적 HTML에 Vue의 반응형 시스템과 이벤트 핸들러를 연결하는 과정이다.
```
서버 HTML: <button>클릭</button> ← 이벤트 없음
↓ Hydration
클라이언트: <button @click="handleClick">클릭</button> ← 이벤트 연결됨
```
### Hydration 불일치(Mismatch) 주의
서버와 클라이언트에서 다른 결과를 렌더링하면 경고가 발생한다.
```vue
<script setup>
// ❌ 문제: 서버(undefined)와 클라이언트(실제값) 결과가 다름
const isClient = ref(typeof window !== 'undefined')
// ✅ 해결: ClientOnly 컴포넌트 사용
</script>
<template>
<!-- 브라우저에서만 렌더링해야 하는 컴포넌트 -->
<ClientOnly>
<BrowserOnlyComponent />
<template #fallback>
<p>로딩 ...</p>
</template>
</ClientOnly>
</template>
```
---
## Nuxt 전용 훅 (useNuxtApp)
```vue
<script setup>
const nuxtApp = useNuxtApp()
// 페이지 전환 이벤트
nuxtApp.hook('page:start', () => {
console.log('페이지 전환 시작')
})
nuxtApp.hook('page:finish', () => {
console.log('페이지 전환 완료')
})
</script>
```
---
## 요약
```
서버 실행: setup() → onServerPrefetch()
HTML 생성 및 전송
클라이언트 수신: HTML 즉시 표시
Hydration (JS 연결)
setup() → onBeforeMount() → onMounted()
이후 SPA처럼 동작
```