Fast Refresh 무한루프

Next JS 사용 중 위와 같은 현상을 접했다.

클라이언트 컴포넌트에서 api 경로에 접근해서 데이터를 가져오는 테스트를 하던 중이었는데, 화면은 이상 없이 로딩되었지만 콘솔을 보니 저렇게 Fast Refresh라는 로그가 계속 반복되고 있었다.

 

Fast Refresh는 웹소켓이 연결을 유지하는 것처럼 특정한 상황이라면 정상적일 수도 있으나, 이번 경우는 단순히 1회성으로 데이터를 가져오는 화면이었고 refresh 속도도 매우 빨랐기에 불필요한 현상이었다.

당장은 문제가 없을지라도 향후 리렌더링이 무한으로 발생하는 등 이슈가 될 수 있기에 코드를 검토해보았다.

 

'use client';

import { useEffect, useState } from 'react';

export default function Fetch() {
  const [user, setUser] = useState({id: null});
  useEffect(() => {
    fetch(process.env.NEXT_PUBLIC_API_URL + '/api/1')
      .then(type => type.json())
      .then(result => {
        setUser(result);
      })
  });

  return (
    <>
      <h1>/app/sub/fetch/page.js</h1>
      <p>{user.id}</p>
      <a href="/">/app/page.js</a>
    </>
  )
}

 

검토 결과, useEffect 내부에서 setUser를 사용한 것이 무한루프의 원인이었다.

useEffect 내부에서 setUser를 실행하면서 상태가 변경되게 되는데, 상태 변경에 따라 useEffect가 재실행되면서 무한 루프가 발생했다.

useEffect() 실행 set User 사용
컴포넌트 리렌더링 user 상태 변경

(시계방향으로 순회)

 

그래서 useEffect에 의존성 배열을 추가함으로써 무한루프 현상을 해결하였다.

의존성 배열을 추가하게 되면 컴포넌트가 처음 렌더링될 때에만 useEffect가 실행되고 되고, 이후에는 상태가 변경되더라도 실행되지 않아 무한루프를 방지할 수 있다.

  useEffect(() => {
    fetch(process.env.NEXT_PUBLIC_API_URL + '/api/1')
      .then(type => type.json())
      .then(result => {
        setUser(result);
      })
  }, []);

 

* 2025.03.14 추가

의존성 배열(Dependency Array)이란, useEffect가 언제 실행될지를 결정하는 배열이다.

예를 들어 user 상태가 변경될 때마다 useEffect를 실행하고 싶다면, useEffect의 두 번째 인자로 [user]를 전달하면 된다.

 

의존성 배열이 없는 경우 (useEffect(() => {...}))
→ 모든 렌더링마다 실행되므로 성능 이슈가 발생할 수 있다.

 

빈 배열 []을 전달하는 경우 (useEffect(() => {...}, []))
→ 처음 렌더링(마운트) 시에만 실행되고, 이후에는 다시 실행되지 않는다.

 

 

끝!

You have a Server Component that imports next/router.

 

Next JS 학습 중 위 에러가 발생했다.

id라는 이름의 path parameter를 사용하려던 중 발생했는데, 그 해결과정을 간략히 정리해보고자 한다.

 



1. 에러 발생 및 설명

먼저, 문제가 된 코드는 아래와 같다.

import { useRouter } from 'next/router';

export default function Id() {
  const router = useRouter();
  const id = Number(router.query.id);

  return (
    <>
      <h1>/pages/sub/[id]/page.js</h1>
      <p>Parameter id: {id}</p>
      <a href="/">/pages/page.js</a>
    </>
  );
}

/sub/[id] 라는 경로로 접근했을 때, next/router의 useRouter를 활용하여 query 값에서 id를 찾아내는 구조이다.

하지만 위 코드를 컴파일하던 중 You have a Server Component that imports next/router, 즉  서버 컴포넌트에서 next/router를 사용했다는 에러가 발생했다.

 

알아보니 next.js 13 버전부터 기존 pages 폴더에서 app 폴더 기반 라우팅 시스템으로 바뀌었는데, 이와 함께 서버 컴포넌트와 클라이언트 컴포넌트를 구분하기 시작했다. next/router의 useRouter는 클라이언트 전용 훅으로서, 클라이언트 컴포넌트에서만 사용이 가능하기 때문에 서버 컴포넌트에서 호출 시 위 에러가 발생한다.


2. 해결

다른 블로그를 참고했을 때 서버 컴포넌트에서는 next/router가 대신 next/navigation을 사용해야 한다고 하여 적용해보았다. 하지만 next/navigation의 useRouter는 query를 포함하고 있지 않아 고민하던 중, 아래와 같이 pathname을 활용하는 방식으로 먼저 접근해보았다.

'use client';

import { usePathname } from 'next/navigation';

export default function IdPage() {
  const pathname = usePathname();
  const id = pathname.split("/")[2];

  return (
    <div>
      <h1>/pages/sub/[id]/page.js</h1>
       <p>Parameter id: {id}</p>
       <a href="/">/pages/page.js</a>
    </div>
  );
}

접근경로인 /sub/[id] 을 pathname으로 받은 후 split()하여 id를 추출하는 방식이다.

 

하지만 위와 같은 방식은 url이 변경될 때마다 코드의 수정이 필요하다.

그래서 좀 더 알아보니, 간단한 해결책이 나왔다. 알고보니 app 폴더 기반 라우팅 시스템에서는 params 객체를 통해 path parameter 값을 받아올 수 있었다.

따라 아래와 같이 params 객체에서 id를 추출하는 방식을 채택했다.

export default function Id({ params }) {
  const { id } = params;

  return (
    <>
      <h1>/pages/sub/[id]/page.js</h1>
      <p>Parameter id: {id}</p>
      <a href="/">/pages/page.js</a>
    </>
  );
}

3. 서버 컴포넌트 & 클라이언트 컴포넌트

서버 컴포넌트

서버 컴포넌트는 서버에서만 실행되는 컴포넌트로 클라이언트 측에서 렌더링되는 JavaScript 코드를 보내지 않고, 서버에서 데이터를 처리하거나 렌더링을 한 뒤 HTML을 클라이언트로 전송한다. Next JS에서는 별다른 선언을 하지 않으면 서버 컴포넌트로 간주한다.

  • 서버에서 동적으로 데이터를 처리하고 렌더링
  • 클라이언트에서 JavaScript를 실행하지 않으며 필요한 경우 React의 상태 업데이트나 이벤트 핸들러 없이 서버에서 완료된 HTML을 클라이언트로 전송
  • 서버 컴포넌트는 클라이언트 사이드 라이브러리나 API 호출과 같은 동적 JavaScript 동작을 할 수 없으며, 대신 서버에서 데이터를 가져와서 HTML을 렌더링

 

클라이언트 컴포넌트

클라이언트 컴포넌트는 클라이언트 브라우저에서 실행되는 컴포넌트이며, 상태 관리, 이벤트 핸들링, 클라이언트 측 API 요청, 동적 렌더링 등을 클라이언트에서 처리할 수 있다. 최상단에 'use client'를 적음으로써 클라이언트 컴포넌트를 선언할 수 있다.

  • 클라이언트에서 JavaScript를 실행하여 동적인 콘텐츠를 표시
  • 상태(state)를 관리하거나 이벤트 핸들러를 통해 동작을 처리
  • 클라이언트에서 useState, useEffect, useRouter와 같은 React 훅을 사용하여 UI를 동적으로 업데이트

 

서버 컴포넌트와 클라이언트 컴포넌트의 구분 이유

  • 서버 컴포넌트는 최적화된 서버 사이드 렌더링(SSR)을 통해 페이지를 클라이언트에게 전달하고, 불필요한 JavaScript 번들을 줄여 페이지 로딩 속도를 개선
  • 클라이언트 컴포넌트는 상태 관리, 이벤트 처리, 동적 UI 업데이트를 클라이언트에서 처리하게 함으로써 사용자 경험 향상

끝!

'Languages > Next.js' 카테고리의 다른 글

[NextJS] Fast Refresh 무한루프 해결  (0) 2025.02.18

+ Recent posts