Published on

Nextjs(app-router) - data fetching

Authors
  • avatar
    Name
    Inhwan Cho
    Twitter

Nextjs의 랜더링 방식

data fetch를 설명하기 전에 Next.js의 주요 랜더링 방식에 대하여 간략하게 설명하고 넘어가겠습니다.

Next.js는 다양한 렌더링 방식을 지원하여, 애플리케이션의 요구 사항과 특성에 맞게 최적화된 사용자 경험을 제공할 수 있도록 합니다.

그 중에서 3가지를 살펴보겠습니다.

랜더링설명장점단점적합한 사용 사례
SSG빌드 타임에 HTML 페이지를 미리 생성합니다.빠른 로딩 속도, SEO 최적화콘텐츠 업데이트 시 재빌드 필요, 실시간 데이터 반영 어려움자주 변경되지 않는 콘텐츠, 블로그, 마케팅 페이지
SSR요청이 있을 때마다 서버에서 HTML 페이지를 생성합니다.실시간 데이터 반영, SEO 최적화높은 서버 부하, SSG에 비해 느린 로딩 속도주기적으로 업데이트되는 콘텐츠, 소셜 미디어 피드
CSR클라이언트 측에서 JavaScript를 사용해 동적으로 데이터를 불러오고 렌더링합니다.사용자 입력에 즉각적인 반응, 더 나은 사용자 경험SEO에 불리, 초기 로딩 속도 저하싱글 페이지 애플리케이션(SPA), 대시보드, 관리자 인터페이스

요약

  • SSG(Static Site Generation)는 사전에 페이지를 생성하여 빠른 로딩 속도와 SEO 이점을 제공하지만, 콘텐츠의 실시간 업데이트에는 한계가 있습니다.

  • SSR(Server Side Rendering)은 각 요청마다 최신의 데이터로 페이지를 생성하여 제공하기 때문에 실시간 콘텐츠 반영에 유리합니다.

  • CSR(Client Side Rendering)은 페이지의 대부분을 클라이언트 측에서 렌더링하여, 사용자와의 인터랙션이 많은 애플리케이션에 적합합니다. 초기 로딩은 느릴 수 있지만, 사용자 경험은 개선됩니다.

SSG(Static Site Generation)

  • cache : "force-cache"옵션을 주면 SSG가 됩니다. - default값이기 때문에 없어도 됩니다.

  • server component

app/page.jsx
const fetchPosts = async () => {
  const response = await fetch("https://goweather.herokuapp.com/weather/London", {
    // cache: "force-cache",
    // 기본값='force-cache', => SSG
    // 'no-store' => SSR
  });
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json();
};

export default async function Posts() {
  const data = await fetchPosts();

  return (
    <div>{data.temperature}</div>
  );
};

SSR(Server Side Rendering)

  • cache : "no-store"옵션을 주면 SSR가 됩니다.

  • server component

Client Component

  • use라는 훅을 사용했지만 use 함수안에 fetch를 사용하는것은 컴포넌트가 여러번 re-rendering될 수 있기 때문에 추천하지 않습니다.
  • client에서 fetch를 하려면 SWR or TanStack Query(react-query)를 사용하는것을 추천드립니다.
app/page.jsx
'use client'
import { use } from "react";

async function fetchPosts() {
  const response = await fetch("https://goweather.herokuapp.com/weather/London")
  return response.json();
};

export default function Posts() {
  const data = use(fetchPosts())
  return (
    <main>
      <p>data</p>
      <p>{data.temperature}</p>
      <p>{data.wind}</p>
    </main>
  );
};

ISR(Incremental Static Regeneration)

  • 재검증은 2가지 방법이 있습니다 [시간 기반의 재검증, 요청 기반 재검증]

시간 기반 재검증 : fetch('abc' , { next: { revalidate : 3000 } }) revalidate 옵션을 넣어 새로 업데이트 하면 됩니다.

요청 기반 재검증 : revalidatePath또는 revalidateTag설정하여 요청이 있을 시, 재검증합니다.

재검증 예시

  1. 페이지에서의 데이터 fetch 및 taging
app/page.tsx
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // 데이터 처리...
}
  1. 서버 액션에서 재검증
app/actions.ts
'use server'

import { revalidateTag } from 'next/cache'

export default async function action() {
  revalidateTag('collection')
}
  • collection 태그가 달린 모든 캐시 항목을 재검증합니다.

캐싱에서 제외하는 조건

  1. fetch 요청에 cache: 'no-store' 옵션이 추가된 경우.
  2. fetch 요청에 revalidate: 0 옵션이 추가된 경우.
  3. POST 메소드를 사용하는 라우터 핸들러 안에 fetch 요청이 있는 경우.
  4. 헤더나 쿠키의 사용 후에 이루어지는 fetch 요청의 경우.
  5. const dynamic = 'force-dynamic' 라우트 세그먼트 옵션을 사용하는 경우.
  6. fetchCache 라우트 세그먼트 옵션이 기본적으로 캐시를 건너뛰도록 설정된 경우.
  7. Authorization 또는 Cookie 헤더를 사용하고, 컴포넌트 트리에서 위에 캐시되지 않은 요청이 있는 fetch 요청의 경우.

third-party(ORM, DB, ...)를 사용하여 데이터 가져오기

  1. Route Segment Config 공식문서를 설정하여 캐싱 및 재검증을 설정 가능.
  • 캐싱과 재검증 구성: 라우트 세그먼트 구성 옵션(Route Segment Config Option)과 React의 cache 함수를 사용하여 캐싱 및 재검증 행위를 구성할 수 있습니다.
page.tsx
// layout.tsx | page.tsx | route.ts에서 모두 사용 가능
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
export const maxDuration = 5

export default function MyComponent() {}
  1. 정적, 동적 재검증
  • 정적 vs 동적 렌더링: 데이터의 캐싱 여부는 라우트 세그먼트가 정적으로 렌더링되는지 동적으로 렌더링되는지에 따라 달라집니다.
  • 정적 세그먼트: 요청 결과는 캐시되고, 라우트 세그먼트의 일부로 재검증됩니다.
  • 동적 세그먼트: 요청 결과는 캐시되지 않으며, 세그먼트가 렌더링될 때마다 데이터를 새로 가져옵니다.

예시

app/utils.ts
import { cache } from 'react'

export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})
app/item/[id]/layout.tsx
// page.tsx 의 경우도 비슷합니다.
import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // revalidate the data at most every hour

export default async function Layout({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}

참고

nextjs 공식 문서