Nuxt에서 highcharts-vue를 사용하기 위해 다음과 같이 plugin으로 주입해주었습니다.
// plugins/highcharts.client.ts
import HighchartsVue from 'highcharts-vue';
import 'highcharts/modules/accessibility';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(HighchartsVue);
});
Highcharts는 Community 버전으로 CSR(Client Side Rendering)만 지원되기 때문에 파일명을 highcharts.client.ts로 지정하여 클라이언트에서만 로드되도록 처리했습니다.
실제 사용하는 컴포넌트에서도 안전하게 <ClientOnly>로 감싸서 사용하였습니다.
<ClientOnly>
<highcharts :options="chartOptions" />
</ClientOnly>
하지만 다음과 같은 SSR 관련 오류가 계속 발생했습니다.
WARN [Vue warn]: Failed to resolve component: highcharts
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
at <ComplaintWidget data-v-inspector="pages/main/index.vue:28:9" >
at <Index data-v-inspector="layouts/main-layout.vue:7:11" onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > >
at <Anonymous key="/main" vnode= { __v_isVNode: true,
__v_skip: true,
type:
{ __name: 'index',
setup: [Function (anonymous)],
ssrRender: [Function: _sfc_ssrRender],
__scopeId: 'data-v-d311227b',
__file:
혹시 자동 등록이 되지 않는 문제인가 싶어 아래와 같이 nuxt.config.ts에 수동으로 등록해보았습니다.
export default defineNuxtConfig({
plugins: [
{ src: '~/plugins/highcharts.client.ts', ssr: false, mode: 'client' }
]
});
하지만 여전히 동일한 오류가 발생했습니다.
문제는 Highcharts를 사용하는 컴포넌트 구조에 있었습니다. 당시의 코드 구조는 다음과 같았습니다.
<template>
<WidgetContainer title="분야별 민원현황" class="complaint-widget">
<div class="complaint-widget__chart">
<ClientOnly>
<highcharts :options="chartOptions" />
</ClientOnly>
</div>
</WidgetContainer>
</template>
WidgetContainer는 slot을 받는 컴포넌트인 게 포인트였습니다.
이 구조에서는 <ClientOnly>로 감쌌음에도 불구하고 highcharts 컴포넌트가 SSR 시점에 이미 slot으로 전달되었기 때문에 Nuxt가 SSR에서 해당 컴포넌트를 찾으려고 시도했습니다.
따라서 <ClientOnly>는 slot 안에서 기대하는 대로 동작하지 않았습니다.
문제를 해결하기 위해 <ClientOnly>를 더 바깥으로 옮겨, 전체 위젯을 클라이언트에서만 렌더링되도록 감쌌습니다.
<template>
<ClientOnly>
<WidgetContainer title="분야별 민원현황" class="complaint-widget">
<div class="complaint-widget__chart">
<highcharts :options="chartOptions" />
</div>
</WidgetContainer>
</ClientOnly>
</template>
이 방법으로는 SSR 오류는 해결되었지만, 새로운 문제가 발생했습니다.
위젯 자체를 클라이언트에서 렌더링하게 되면서, 다른 SSR된 위젯들보다 늦게 뜨는 렌더링 지연이 눈에 띄게 되었습니다.
위젯 컨테이너는 SSR로 먼저 렌더링하고, Highcharts 부분만 차트 컴포넌트 내부에서 ClientOnly로 분리해 렌더링하는 방식으로 개선하였습니다.
<!-- DonutChart.vue -->
<template>
<ClientOnly>
<highcharts ref="chartRef" :options="chartOptions" :update-args="[true, true, true]" />
</ClientOnly>
</template>
<!-- DonutChart를 사용하는 상위 컴포넌트 -->
<template>
<WidgetContainer title="분야별 민원현황" :date="criteriaDate" class="complaint-widget">
<NuxtLink :to="PATH_INDICATORS_COMPLAINT" class="widget-wrap" title="분야별 민원현황 상세 보기">
<ErrorBoundary :error="complaintError">
<div class="complaint-widget__chart">
<DonutChart :data="complaintData" :custom-options="options" :use-download="false" />
</div>
</ErrorBoundary>
</NuxtLink>
</WidgetContainer>
</template>
이 구조가 가장 자연스럽고 빠르게 렌더링되는 결과를 보여주었으며,
SSR 환경에서 Highcharts를 사용할 때의 오류도 완전히 해결되었습니다.
Nuxt에서 Highcharts와 같은 CSR 전용 라이브러리를 사용할 때는
렌더링 위치와 slot 전달 방식에 따른 SSR 동작을 반드시 고려해야 합니다.
같은 문제를 겪고 계신 분께 도움이 되었기를 바랍니다!