Ch 03. CSR 전용 컴포넌트 — ClientOnly
SSR 환경에서는 Node.js가 Vue 컴포넌트를 HTML 문자열로 변환합니다. 이때 window, document, localStorage 같은 브라우저 전용 API는 존재하지 않습니다. 이런 API를 사용하는 컴포넌트를 아무 처리 없이 그대로 쓰면 서버 빌드가 실패하거나 런타임 오류가 발생합니다.
ClientOnly로 브라우저 전용 렌더링 지정하기
Nuxt는 <ClientOnly> 컴포넌트를 기본으로 제공합니다. 이 태그로 감싼 내용은 서버에서 렌더링하지 않고, 클라이언트에서만 마운트합니다.
<template>
<ClientOnly>
<BrowserOnlyChart />
<template #fallback>
<p>차트를 불러오는 중입니다.</p>
</template>
</ClientOnly>
</template>
#fallback 슬롯에 넣은 내용은 서버 렌더링 시점과 클라이언트 하이드레이션이 완료되기 전까지 보여줍니다. 빈 화면 대신 로딩 메시지나 스켈레톤 UI를 표시하면 사용자 경험이 좋아집니다.
onMounted에서 브라우저 API 접근하기
컴포넌트 전체를 <ClientOnly>로 감싸지 않아도 되는 경우라면 onMounted 안에서 브라우저 API를 사용합니다. onMounted는 클라이언트에서만 실행되기 때문에 안전합니다.
// composables/useLocalStorage.tsimport { onMounted, ref } from 'vue'export function useLocalStorage(key: string) { const value = ref<string | null>(null) onMounted(() => { value.value = localStorage.getItem(key) }) return value}
import.meta.client로 조건부 실행하기
분기가 필요할 때는 import.meta.client(또는 process.client)를 사용합니다.
if (import.meta.client) { // 이 블록은 브라우저에서만 실행됩니다 const saved = localStorage.getItem('theme') applyTheme(saved ?? 'light')}
import.meta.server는 반대로 서버에서만 실행되어야 하는 코드에 씁니다.
어떤 방법을 선택할까
| 상황 | 권장 방법 |
|---|---|
| 외부 차트·지도 라이브러리 컴포넌트 | <ClientOnly> |
| 컴포넌트 초기화 시 한 번만 접근 | onMounted |
| 스크립트 내 조건부 분기 | import.meta.client |
세 방법 모두 "서버에서는 실행하지 않는다"는 목적은 같습니다. 코드 위치와 가독성에 따라 골라서 사용하면 됩니다.