- Published on
Nextjs(app-router) - data fetching
- Authors
- Name
- Inhwan Cho
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
orTanStack 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
설정하여 요청이 있을 시, 재검증합니다.
재검증 예시
- 페이지에서의 데이터 fetch 및 taging
app/page.tsx
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['collection'] } })
const data = await res.json()
// 데이터 처리...
}
- 서버 액션에서 재검증
app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export default async function action() {
revalidateTag('collection')
}
collection
태그가 달린 모든 캐시 항목을 재검증합니다.
캐싱에서 제외하는 조건
- fetch 요청에 cache: 'no-store' 옵션이 추가된 경우.
- fetch 요청에 revalidate: 0 옵션이 추가된 경우.
- POST 메소드를 사용하는 라우터 핸들러 안에 fetch 요청이 있는 경우.
- 헤더나 쿠키의 사용 후에 이루어지는 fetch 요청의 경우.
- const dynamic = 'force-dynamic' 라우트 세그먼트 옵션을 사용하는 경우.
- fetchCache 라우트 세그먼트 옵션이 기본적으로 캐시를 건너뛰도록 설정된 경우.
- Authorization 또는 Cookie 헤더를 사용하고, 컴포넌트 트리에서 위에 캐시되지 않은 요청이 있는 fetch 요청의 경우.
third-party(ORM, DB, ...)를 사용하여 데이터 가져오기
- 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() {}
- 정적, 동적 재검증
- 정적 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)
// ...
}