[Next.js 13] 클라이언트 컴포넌트가 정말 클라이언트 사이드에서만 렌더링 될까 ?
◾️문제 발생
react-draft-wysiwyg 를 사용한 컴포넌트를 페이지 컴포넌트에 import했습니다. 페이지는 성공적으로 렌더링 되었지만 터미널에서 에러를 확인할 수 있었습니다. 빌드시에도 동일한 에러가 발생합니다.

use client를 최상단에 선언했었기에, 클리이언트 컴포넌라고 생각했고 서버 컴포넌트에서 날 법한 프리렌더링 에러가 발생하는 것이 이해가 가지 않았습니다.
◾️ 문제 원인
문제가 발생한 이유는 다음과 같습니다.
- Next.js 13 컴포넌트는 서버에서 사전 렌더링되고 클라이언트에서 하이드레이션된다.
- 따라서
use client를 선언한다고해서 클라이언트에서만 렌더링 되는 것은 아니다.
컴포넌트에서 window객체에 접근하는데, 서버에서 사전 렌더링되는 과정에서 window객체를 찾지 못해 에러가 발생한 것이었습니다. 클라이언트 단에서만 실행되어야하는 코드는 서버에서 실행되지 않도록 설정이 필요합니다.
◾️ 문제 해결
✔️ 방법 1. dynamic import (동적 임포트)로 SSR 옵션 끄기
const NoticeForm = dynamic(() => import('@/components/notice/NoticeForm'), {
ssr: false,
});
Next.js 의 dynamic import 기능을 활용해 서버 측 렌더링을 비활성화했습니다. dynamic import는 컴포넌트 외부에서 선언해야합니다.
다만 이 방법은 해당 에러가 발생하는 모든 컴포넌트를 동적으로 import해야하는 번거로움이 있었고, 에러 원인을 찾던 중 전역으로 설정해주는 방법이 있었습니다.
✔️ 방법 2. 컨텍스트를 사용한 custom hook 만들기
동적 임포트 매커니즘을 컴포넌트마다 반복적으로 구현하는 대신, 컨텍스트를 사용해 전역에서 관리하는 방법도 있습니다.
useEffect와 useState 훅을 사용한 커스텀 훅을 통해 클라이언트 환경을 실행조건으로 설정할 수 있었습니다.
이렇게하면 서버에서의 사전렌더링과 클라이언트의 첫번째 렌더링이 일치해 하이드레이션 오류도 방지할 수 있습니다.
💾 useIsClient.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
const IsClientCtx = createContext(false);
export const IsClientCtxProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const [isClient, setIsClient] = useState(false);
useEffect(() => setIsClient(true), []);
return (
<IsClientCtx.Provider value={isClient}>{children}</IsClientCtx.Provider>
);
};
export function useIsClient() {
return useContext(IsClientCtx);
}
💾 app/layout.tsx
import type { Metadata } from 'next';
import './globals.css';
import { IsClientCtxProvider } from '@/hooks/useIsClient';
export const metadata: Metadata = {
title: 'Project',
description: 'Generated by create next app',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<IsClientCtxProvider>
{children}
</IsClientCtxProvider>
</body>
</html>
);
}
💾 사용예시
import { useIsClient } from './is-client-ctx';
function MyComponent() {
const isClient = useIsClient();
const scrollPosition = useScrollPosition(); // 예시를 위한 가상 훅
return (
<>
{scrollPosition >= 0 && <FirstModule />}
{isClient && scrollPosition >= window.innerHeight * 2 && <SecondModule />}
</>
);
}
useIsClient.tsx 훅을 통해 서버에서 렌더링할 수 있는 부분을 먼저 렌더링 후, 클라이언트 조건에 따라 업데이트 (두번째 렌더링)을 실행합니다. 이 방식을 통해 하이드레이션 오류를 피하면서 필요한 경우 클라이언트 측 업데이트를 수행할 수 있었습니다.
💬 최신 댓글