Post

Next.js와 React Query를 활용한 SSR 적용 방법

Next.js와 React Query를 활용한 SSR 적용 방법

Next.js와 React Query를 활용한 SSR 적용 방법

Next.js에서 React Query를 활용하여 서버 사이드 렌더링(SSR)을 적용하는 방법을 설명한다. 이를 통해 SEO 성능을 개선하고, 초기 로딩 속도를 향상시킬 수 있다.


1. SSR에서 React Query를 활용하는 이유

  • 서버에서 데이터를 미리 가져와 클라이언트에서 Hydration하여 SEO 최적화 가능
  • 초기 렌더링 속도가 빨라져 사용자 경험 개선
  • Next.js의 fetch 함수와 React Query의 useQuery를 함께 활용하여 데이터 관리 최적화

2. SSR을 적용한 page.tsx 수정

아래 코드는 홈페이지에서 SSR을 적용하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// src/app/(home)/page.tsx
import {
  HydrationBoundary,
  QueryClient,
  dehydrate
} from "@tanstack/react-query";
import { getProducts } from "@/hooks/api/useProducts";
import Container from "@/components/Container";
import Categories from "@/components/Categories";
import Sidebar from "@/components/Sidebar";
import Products from "@/components/Products";
import FloatingButton from "@/components/FloatingButton";
import { FaPlus } from "react-icons/fa";

export default async function Home({
  searchParams
}: {
  searchParams: Record<string, string>;
}) {
  const queryClient = new QueryClient();

  // 서버에서 데이터를 미리 가져와 QueryClient에 캐싱
  await queryClient.prefetchQuery({
    queryKey: ["products", searchParams],
    queryFn: () => getProducts(searchParams)
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <Container>
        <Categories />
        <div className="flex flex-col md:flex-row w-full">
          {Object.keys(searchParams).length !== 0 && <Sidebar />}
          <Products searchParams={searchParams} />
        </div>
        <FloatingButton href="/products/upload">
          <FaPlus />
        </FloatingButton>
      </Container>
    </HydrationBoundary>
  );
}

🔹 SSR을 위한 핵심 과정

  1. getProducts를 통해 서버에서 데이터를 미리 가져옴
  2. queryClient.prefetchQuery 로 데이터를 미리 캐싱하여 클라이언트가 동일한 데이터를 다시 요청하지 않도록 함
  3. dehydrate(queryClient) 로 데이터를 직렬화하여 클라이언트로 전송
  4. 클라이언트에서 HydrationBoundary를 통해 데이터를 React Query로 다시 불러옴

3. getProducts 함수 구현

서버에서 데이터를 가져오는 getProducts 함수를 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/hooks/api/useProducts.ts
export const getProducts = async (searchParams: Record<string, string>) => {
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_URL}/api/products?${new URLSearchParams(
      searchParams
    ).toString()}`,
    {
      cache: "no-store" // 최신 데이터를 가져오기 위해 캐싱 방지
    }
  );

  if (!response.ok) throw new Error("네트워크 응답이 올바르지 않습니다");

  return response.json();
};

🔹 fetch 옵션 설명

  • cache: "no-store": SSR에서 최신 데이터를 가져오기 위해 캐싱을 방지함
  • new URLSearchParams(searchParams).toString(): URL에 쿼리스트링을 동적으로 추가하여 API 요청

4. loading.tsx 수정 또는 제거

Next.js에서 loading.tsx가 존재하면 Suspense로 감싸지기 때문에 SSR이 정상적으로 동작하지 않을 수 있다. 이를 해결하기 위해 loading.tsx를 다음과 같이 수정하거나 제거한다.

1
2
3
4
// src/app/(home)/loading.tsx
export default function Loading() {
  return null; // 최소한의 로딩 UI 또는 제거
}

✅ 해결 방법

  • loading.tsx를 제거하면 SSR이 정상적으로 동작
  • 혹은 Suspense를 특정 컴포넌트에만 적용하도록 변경

5. getCategories 함수 수정

서버에서 getCategories API를 호출할 때 절대 URL을 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/hooks/api/useCategories.ts
export const getCategories = async () => {
  const baseUrl =
    process.env.NODE_ENV === "development"
      ? "http://localhost:3000"
      : process.env.NEXT_PUBLIC_API_URL;

  const response = await fetch(`${baseUrl}/api/categories`, {
    cache: "no-store"
  });

  if (!response.ok) {
    throw new Error("Failed to fetch categories");
  }

  return response.json();
};

// 클라이언트에서 사용하는 훅
export const useCategories = () => {
  return useQuery({
    queryKey: ["categories"],
    queryFn: () => axios.get("/api/categories").then((res) => res.data)
  });
};

🔹 수정 이유

  • 서버 컴포넌트에서 상대 경로(/api/categories)를 사용할 경우, SSR 시 문제가 발생
  • 절대 URL(http://localhost:3000 또는 NEXT_PUBLIC_API_URL)을 사용해야 SSR에서 정상적으로 동작

6. SSR과 React Query 적용 결과

초기 로딩 속도 개선: 데이터가 서버에서 미리 로드되므로 클라이언트에서 추가 요청 없이 바로 렌더링

SEO 향상: SSR을 통해 검색 엔진이 HTML에 포함된 데이터를 바로 크롤링 가능

클라이언트에서 Hydration: HydrationBoundary를 활용하여 React Query 캐싱을 유지하면서 동작

Suspense를 활용한 로딩 최적화: SSR을 방해하지 않으면서 특정 컴포넌트에서 로딩 UI 제공


결론

Next.js에서 React Query를 활용하여 SSR을 적용하는 방법을 정리하면 다음과 같다.

  1. 서버에서 데이터를 미리 가져와 QueryClient에 캐싱
  2. 클라이언트에서 HydrationBoundary를 통해 데이터 유지
  3. 절대 URL을 사용하여 서버에서도 API 요청 가능하도록 설정
  4. Suspense를 특정 컴포넌트에 적용하여 SSR 방해 최소화
  5. 캐싱을 위한 cache: "no-store" 설정으로 최신 데이터 유지

이를 통해 빠른 초기 로딩, SEO 최적화, 성능 개선을 달성할 수 있다. 🚀

This post is licensed under CC BY 4.0 by the author.