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

3.9 KiB

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()에서의 서버/클라이언트 분기

<script setup>
// setup()은 서버와 클라이언트 양쪽에서 실행됨
const config = useRuntimeConfig()

// 서버/클라이언트 환경 확인
if (import.meta.server) {
  console.log('서버에서만 출력')
}

if (import.meta.client) {
  console.log('클라이언트에서만 출력')
}
</script>

onServerPrefetch — 서버에서 데이터 미리 준비

<script setup>
const data = ref(null)

// 서버에서 먼저 데이터를 가져오고, 클라이언트로 상태 전달
onServerPrefetch(async () => {
  data.value = await $fetch('/api/products')
})
</script>

useAsyncData / useFetch가 내부적으로 onServerPrefetch를 사용한다. 직접 쓸 일은 드물지만, 동작 원리 이해에 중요하다.

onMounted — 브라우저 전용 작업

<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) 주의

서버와 클라이언트에서 다른 결과를 렌더링하면 경고가 발생한다.

<script setup>
// ❌ 문제: 서버(undefined)와 클라이언트(실제값) 결과가 다름
const isClient = ref(typeof window !== 'undefined')

// ✅ 해결: ClientOnly 컴포넌트 사용
</script>

<template>
  <!-- 브라우저에서만 렌더링해야 하는 컴포넌트 -->
  <ClientOnly>
    <BrowserOnlyComponent />
    <template #fallback>
      <p>로딩 ...</p>
    </template>
  </ClientOnly>
</template>

Nuxt 전용 훅 (useNuxtApp)

<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처럼 동작