[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 훅을 통해 서버에서 렌더링할 수 있는 부분을 먼저 렌더링 후, 클라이언트 조건에 따라 업데이트 (두번째 렌더링)을 실행합니다. 이 방식을 통해 하이드레이션 오류를 피하면서 필요한 경우 클라이언트 측 업데이트를 수행할 수 있었습니다.
Leave a comment