Inflearn brand logo image

인프런 커뮤니티 질문&답변

dubu777님의 프로필 이미지
dubu777

작성한 질문수

Next + React Query로 SNS 서비스 만들기

Suspense로 Streaming하여 최적화하기(feat. loading.tsx, error.tsx)

useSuspenseQuery 사용 시 SSR 401 이슈 관련

작성

·

30

·

수정됨

0

안녕하세요 강사님 좋은 강의 잘 듣고 있습니다!

useSuspenseQuery를 사용 중에 고민이 생겨서 질문 드립니다.

제가 구현하고 있는 프로젝트의 대부분 API는 인증이 필요합니다.
refresh token은 http only 쿠키로 관리하고, access token은 쿠키에 저장하고, 요청 헤더에 넣어 인증을 하고 있습니다.


useSuspenseQuery를 사용하는 페이지에서 SSR 단계에 401 에러가 발생합니다.

이후 CSR 전환 시에는 정상적으로 데이터를 불러와 화면은 최종적으로 렌더링됩니다.

해당 페이지를 dynamic import로 CSR 강제하면 401이 발생하지 않습니다.

 

제가 추정한 원인은

  • 클라이언트 컴포넌트 내부라도 초기 렌더링 시 SSR 패스에서 useSuspenseQuery가 실행되어 서버에서 API 요청이 발생.

  • 이때 공통 axios 인스턴스가 클라이언트 전용 방식으로 쿠키를 읽어 헤더에 토큰을 주입하도록 구현되어 있어, 서버 환경에서는 토큰을 읽지 못해 401이 발생하는 것으로 판단했습니다.

 

해결을 위해 시도한 방법

  • prefetchQuery +useSuspenseQuery 조합으로, prefetchQuery 단계에서 next/headers를 통해 서버 환경에서 토큰을 읽어 주입하면 401이 사라졌습니다.

  • 다만, 모든 useSuspenseQuery 호출 지점마다 prefetchQuery를 추가하는 것은 과도하다고 느껴 대안을 모색 중입니다.

 

질문 사항

  1. 왜 SSR에서 실행되나요?

    • 클라이언트 컴포넌트 내부에서 호출하는데도 useSuspenseQuery가 SSR 렌더링 단계에서 동작하는 메커니즘을 정확히 이해하고 싶습니다.

  2. useSuspenseQuery의 단점/주의점

    • 강의에서는 주 사용을 권장해 주셨는데, 모든 데이터 관리에 useQuery 대신 useSuspenseQuery 사용하는게 좋은건지 실제 서비스에서 고려해야 할 단점이나 주의사항이 궁금합니다.

  3. 401을 피하는 권장 패턴

    • 제 환경처럼 서버에서 토큰을 읽지 못해 401이 나는 경우,
      제가 시도한 방법 인prefetchQuery 적용 외에 권장되는 표준 패턴이 있을까요?

  4. prefetchQuery를 여러 곳에서 사용할 때의 리스크

    • 여러 페이지/쿼리에서 prefetchQuery를 널리 적용하면 TTFB 지연, 직렬화된 캐시의 HTML 페이로드 증가, 중복 호출 등의 문제가 커질 수 있을까요?
      그렇다면 적절한 적용 기준이나 완화 전략이 궁금합니다.

    • 그리고 어떤 기준으로 prefetchQuery를 적용하면 좋을지도 궁금합니다.

문서와 블로그, GPT 등을 찾아봤지만 명확히 정리하기 어렵고, 제가 질문을 드릴 수 있는 최고 전문가라고 생각하여 의견을 여쭙니다.
긴글 읽어주셔서 감사합니다!

 

아래 에러가 발생하는 예시 코드 첨부 드립니다. (APage를 CSR로 강제하면 에러 미발생)

import Spinner from "@/components/common/spinner/Spinner";
import A from "@/components/pages/A";
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

interface APageProps {
  params: {
    id: string;
  };
}

export default async function APage({
  params,
}: APageProps) {
  const { id } = params;
  const parsedId = parseInt(id);

  return (
    <ErrorBoundary fallback={<div>에러가 발생했습니다</div>}>
      <Suspense fallback={<Spinner fullscreen />}>
        <A id={parsedId} />
      </Suspense>
    </ErrorBoundary>
  );
}
"use client";
import { useGetAList } from "@/api/A/queries/useGetAList";


interface AProps {
  id: number;
}

export default function A ({ id }: AProps) {
  const { data: probiomeList } = useGetAList(id);
// 생략
import { queryKeys } from "@/constants/queryKeys";
import { UseSuspenseQueryCustomOptions } from "@/types";
import { useSuspenseQuery } from "@tanstack/react-query";
import { getAList } from "../A";
import { AList } from "@/types/A";

export function useGetAList(
  id: number,
  queryOptions?: UseSuspenseQueryCustomOptions<AList>
) {
  return useSuspenseQuery({
    queryFn: () => getAList(id),
    queryKey: [
      queryKeys.A.BASE,
      queryKeys.A.GET_A_LIST,
      id,
    ],
    ...queryOptions,
  });
}

답변 1

1

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

이번 강의에 수업노트를 보시면 Suspense랑 useSuspenseQuery는 둘 다 SSR됩니다. SSR이 필요한 경우 쓰시면 됩니다.

SSR시에는 반드시 토큰이 서버에서 접근 가능해야 하고 그러면 보통 쿠키를 많이 사용합니다.

prefetchQuery는 fetch가 넥스트와 호환되는 경우에는

데이터캐시로 요청 횟수를 최적화할 수 있습니다.

dubu777님의 프로필 이미지
dubu777

작성한 질문수

질문하기