Next.js Turbopack dev에서 렌더링이 두 번씩 될 때

Next.js Turbopack dev에서 렌더링이 두 번씩 될 때

dev 실행 시 렌더링/이펙트가 두 번 실행된다?

React Strict Mode

리액트의 StrictMode는 디버그를 위한 몇 가지 기능을 제공해 준다. 그 중에는 이펙트나 렌더링 단계의 버그를 잡기 위해 실행되는 다음과 같은 기능이 이런 문제를 만든다.

  1. 렌더링 단계에서 발생하는 문제를 찾기 위해 컴포넌트가 한 번 더 렌더링된다.
  2. 이펙트 정리 누락으로 발생하는 문제를 찾기 위해 이펙트가 재실행된다.
  3. Deprecated된 API 사용을 탐지한다.

이 중 1번과 2번 때문에 이펙트/렌더링이 두 번씩 실행되는 문제가 발생하게 된다.

발생할 수 있는 문제?

StrictMode는 디버그 단계에서 매우 유용하지만, 간단한 예시로 다음과 같은 코드에서 문제가 발생할 수 있다.

"use client"

import {ReactNode, useEffect, useState} from "react";

export default function ClientComp({children} : {children: ReactNode}) {
    const [count, setCount] = useState(0);

    useEffect(() => {
        setInterval(() => {
            setCount(count => count + 1);
        }, 1000);
    }, []);
    
    return <div>
        <h1>Hello, Next.js!!! {count}</h1>
        {children}
    </div>
}

위 코드는 1초마다 count를 1씩 증가시키고 이를 표시해주는 컴포넌트이다. 그러나 기본적으로 Next.js의 dev 실행 시 reactStrictModetrue로 설정되어 있으므로, 해당 컴포넌트는 두 번 렌더링되고, 컴포넌트의 이펙트들도 두 번 호출된다.

따라서 useEffect 내부의 setInterval 역시 두 번 호출되게 되어 1초마다 count가 1 증가하는 setInterval이 두 번 호출되므로, 결국 1초마다 count가 2씩 증가하게 된다.

해결 방법

Best Solution?

우선, 가장 좋은 해결책은 당연히 StrictMode가 켜져 있어도 문제가 없게 코딩하는 것이다.

"use client"

import {ReactNode, useEffect, useRef, useState} from "react";

export default function ClientComp({children} : {children: ReactNode}) {
    const refInterval = useRef<NodeJS.Timeout>();
    
    const [count, setCount] = useState(0);

    useEffect(() => {
        refInterval.current = setInterval(() => {
            setCount(count => count + 1);
        }, 1000);
        
        return () => {
            if (refInterval.current) {
                clearInterval(refInterval.current);
            }
        }
    }, []);
    
    return <div>
        <h1>Hello, Next.js!!! {count}</h1>
        {children}
    </div>
}

위처럼 cleanup 로직에 올바른 정리 코드를 추가하여 이펙트가 여러번 재호출되더라도 문제가 없도록 작성하는 게 가장 좋다.
위 예제에서는 setInterval이 무한히 쌓이지 않도록 하기 위해 useRef를 사용하여 intervalID를 저장하고, 컴포넌트가 dismount될 때 이를 정리하는 로직을 추가하였다.

차선책

물론 특수한 상황에서는 이러한 StrictMode를 해제해야 할 수도 있다. React의 경우 <StrictMode> 컴포넌트를 제거하면 되지만 Next.js의 경우 next.config.js1를 수정해야 한다.

/** @type {import("next").NextConfig} */
const nextConfig = {
    reactStrictMode: false,
};

export default nextConfig;

위처럼 설정에서 reactStrictModefalse로 설정해주면 next dev로 실행하더라도 StrictMode가 작동하지 않게 된다.


  1. 또는 next.config.mjs or next.config.cjs ↩︎

댓글

이 블로그의 인기 게시물

C# 남아도는 메모리에도 불구하고 OutOfMemoryException이 발생한다면?

USB를 뒤는 괜찮은데 앞에 꽂으면 인식이 힘들다?

MySQL 데이터 타입과 Java 데이터 타입 비교/매칭