본문 바로가기
nextjs

[Nextjs 15] Uncaught Error: Switched to client rendering because the server rendering errored 에러

by spare8433 2025. 2. 7.

에러 발생

nextjs 에서 비동기 요청 과정 중 자연스러운 ui 흐름을 구성하기 위해 useSuspenseQuery + Suspense + react-error-boundary 를 활용하는 중 "Uncaught Error: Switched to client rendering because the server rendering errored" 에러 발생





"Uncaught Error: Switched to client rendering because the server rendering errored" 설명

이 에러는 Next.js에서 발생하며, 서버 사이드 렌더링(SSR) 중에 오류가 발생하여 **클라이언트 사이드 렌더링(CSR) 으로 자동 전환**되었음을 의미합니다.

  • Next.js App Router 환경에서 서버 컴포넌트가 렌더링 도중 실패하면 발생합니다.
  • 서버가 렌더링에 실패하더라도 페이지가 완전히 깨지는 걸 막기 위해, Next.js는 자동으로 CSR로 fallback합니다.










의문점



1. 클라이언트 컴포넌트에서 서버 랜더링?

useSuspenseQuery 를 사용하여 데이터를 fetch 하는 클라이언트 컴포넌트에서 발생한 오류라는 점



해결 : Suspense 사용은 nextjs 에서는 스트리밍을 구현하는 방법 중 하나, 즉 Suspense 로 감싸고 useSuspenseQuery 를 활용하는 과정에 서버에서 data 를 fetch 하게 됨




2. 401 Unauthorized 오류

첫 번째 fetch 과정(server)에서 401 Unauthorized 응답인증을 위한 쿠키를 전달 및 인증 과정에서 에러가 발생하고 이후 두 번째 fetch 과정(client)에서는 정상적인 응답 반환하는 점



해결 : 인증을 위해 쿠키를 사용하는데 서버에서는 쿠키에 접근 할 수 없어 인증정보 서버단계에서 전달하지 못하여 401 Unauthorized 응답하며 클라이언트에서는 정상 응답










해결 과정



1. 쿠키에 접근 할 수 없으니 다르 인증 방법 사용?

storage 나 메모리에 저장해서 쓰는 방법의 경우 각각 보안적인 측면 사용성의 측면에서
보안적인 측면에서 아쉬움
이 있었고, CSR 이 사이트 성격에도 자연스러우므로 jwt 를 쿠키에 담아 사용하는 기존방식을 사용하는 방식이 조금 더 낫다고 판단




2. fetch 상태에 따른 ui 구성 방식 변경



기존 방식

useSuspenseQuery + Suspense + react-error-boundary 를 활용해 데이터를 불러오는 과정 중 loading 과정대체 ui 를 Suspense 의 fallback 으로 지정하고 error 가 발생 시 대체 ui 를 react-error-boundary 의 FallbackComponent 으로 지정



const Page = () => {
  return (
    <ErrorBoundary FallbackComponent={ <error fallback ui /> }>
      <Suspense fallback={ <loading ui /> }>
        <Content />
      </Suspense>
    </ErrorBoundary>
  )
}

const Content = () => {
  const { data } = useSuspenseQuery({
    ...
  })
  return (<div>{data}</div>)
}




개선 방식

useSuspenseQuery + Suspense 를 사용하지 않고 useQuery 를 통해 데이터를 불러오고 loading 과정에서 useQueryisLoading, isSuccess 등의 상태에 맞게 data fetch 한 component 안에서 로딩 ui 를 직접 구현하도록 변경



const Page = () => {
  return (
    <ErrorBoundary FallbackComponent={ <error fallback ui /> }>
      <Content />
    </ErrorBoundary>
  )
}

const Content = () => {
  const { data, isSucess } = useQuery({
    throwOnError:  true, // 렌더링 단계에서 오류를 발생시키고 가장 가까운 오류 경계(error boundary) 로 전파
    ...
  })

  // isLoading 쓰지 않은 이유는 error, loading 일 때 data 타입이 undefined 일 수 있으므로 실제 error 발생시 error boundary 에서 처리하겠지만 type 이 특정되지 않으므로 !isSuccess 를 사용
  if (!isSuccess) return <loading ui/>;

  return (<div>{data}</div>)
}






참고

https://nextjs.org/docs/app/getting-started/fetching-data#with-suspense
https://tanstack.com/query/latest/docs/framework/react/examples/nextjs-suspense-streaming