Data Fetching
13 이전까지 사용되었던 getServerSideProps와 getStaticProps는 이제 잊어라!!..는 아니고.. 13부터 도입된 방법이 앞으로 활발하게 사용되더라도, 레거시 코드에는 이 둘이 남아있을테니 아예 잊어버리는 건 안될 것 같다. (참고: [Next.js] Next.js의 프리 렌더링(pre-rendering) 옵션 3가지 / SSG, SSR, ISR)
이 둘은 이름도 뭔가 장황한 느낌이고, 개인적으로는 어떤 상황에 어떤 걸 써야할지 딱 떠오르지가 않았다. 13부터는 data fetching을 할 때 이 둘 대신 fetch API를 사용하면 된다! getServerSideProps와 getStaticProps와 비슷하게 구현하려면 fetch 메서드의 두 번째 인자로 다음과 같이 옵션 값을 주면 된다.
// 직접 무효화 하기 전까지는 이 request는 캐싱됨.
// `getStaticProps`와 비슷! (즉, 빌드 시점에 fetch)
// `force-cache`가 디폴트 값이므로 생략 가능
fetch(URL, { cache: 'force-cache' });
// 매번 요청 때마다 refetch 됨.
// `getServerSideProps`와 비슷!
fetch(URL, { cache: 'no-store' });
// 이 request는 10초동안 캐싱됨.
// This request should be cached with a lifetime of 10 seconds.
// `revalidate` 옵션을 지정한 `getStaticProps`와 비슷!
fetch(URL, { next: { revalidate: 10 } });
동일하게 fetch 메서드를 사용하면서 옵션 값으로 구분하는 방식이 확실히 이전보다 좀 더 직관적인 것 같다. Next.js 13 App Playground를 Vercel을 통해 직접 배포한 뒤 확인해보면 ssg, ssr, isg 각각에 해당하는 페이지가 있고, 각 페이지에서 어떤 식으로 data fetch가 이루어지는지 소스코드와 Last Rendered 시간을 통해 확인해볼 수 있다.
SSG
- Last Rendered 시간을 보면 앱이 빌드된 시점에 데이터를 fetch 해왔음을 알 수 있다.
// app/ssg/[id]/page.tsx
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }];
}
async function fetchData(params: { id: string }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`,
);
const data = await res.json();
return data;
}
export default async function Page({
params,
}: {
params?: any;
children?: React.ReactNode;
}) {
const data = await fetchData(params);
return (
<div className="space-y-4">
<h1 className="text-2xl font-medium text-gray-100">{data.title}</h1>
<p className="font-medium text-gray-400">{data.body}</p>
</div>
);
}
어차피 'force-cache'가 디폴트라서 옵션은 따로 주지 않았음을 확인할 수 있다.
SSR
- 매 요청 시마다 데이터를 refetch 해오며 그때마다 서버사이드에서 페이지가 생성된다. 이 글 작성한 시점에는 SSR 페이지에서 포스트 클릭하면 500 에러 뜨고 접속이 되지 않아서 Last Rendered 시간은 확인할 수 없었으나.. 아마 페이지가 새로고침될 때마다 refetch를 해서 Last Rendered 시간도 매번 갱신되지 않을까 싶다.
// app/ssr/[id]/page.tsx
async function fetchData(params: { id: string }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`,
{ cache: 'no-store' },
);
const data = await res.json();
return data;
}
export default async function Page({
params,
}: {
params?: any;
children?: React.ReactNode;
}) {
const data = await fetchData(params);
return (
<div className="space-y-4">
<h1 className="text-2xl font-medium text-gray-100">{data.title}</h1>
<p className="font-medium text-gray-400">{data.body}</p>
</div>
);
}
cache 옵션의 값으로 'no-store'을 지정한 것을 확인할 수 있다.
ISR
- 예제에서는 15초를 기준으로 주기적으로 재검증이 일어난다. 만약 15초 이후 해당 페이지에 대한 요청이 다시 들어온다면 데이터를 refetch하고 페이지를 재생성한다.
// app/isr/[id]/page.tsx
export const dynamicParams = true;
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }];
}
async function fetchData(params: { id: string }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`,
{ next: { revalidate: 15 } },
);
const data = await res.json();
return data;
}
export default async function Page({
params,
}: {
params?: any;
children?: React.ReactNode;
}) {
const data = await fetchData(params);
return (
<div className="space-y-4">
<h1 className="text-2xl font-medium text-gray-100">{data.title}</h1>
<p className="font-medium text-gray-400">{data.body}</p>
</div>
);
}
next 옵션의 값으로 revalidate 시간을 15로 설정했다. MDN이랑 소스코드 보면 이 next 옵션은 기본 fetch 메서드에 있던 건 아닌 것 같고 Next.js에서 fetch를 확장해 둔 것 같다.
interface NextFetchRequestConfig {
revalidate?: number | false
}
interface RequestInit {
next?: NextFetchRequestConfig | undefined
}
Server Components
서버 컴포넌트를 이용하여 data fetching을 하는 방식이 유용하다고 하는데, 이 방식을 사용할 때의 장점은 다음과 같다. :
1. 클라이언트 단에서 돌아가지 않는 데이터베이스 및 API 등의 백엔드 서비스에 접근할 수 있다.
2. 보안 키 값들이 클라이언트 단에 드러나지 않도록 지킬 수 있다.
3. data fetching과 렌더링을 동일한 환경에서 수행할 수 있다.
4. 서버에 렌더링을 캐싱할 수 있다.
5. 번들링할 자바스크립트 양을 줄일 수 있다.
클라이언트 컴포넌트가 클라이언트 단에서 렌더링 되는 것과 달리 서버 컴포넌트는 말 그대로 서버 단에서 렌더링된다. 클라이언트는 브라우저를 의미하며, 브라우저는 서버로 요청을 보낸다. 한편 서버는 요청을 받아들일 수 있는 코드를 호스트하는 쪽이며, 요청에 대한 처리가 완료되면 응답을 돌려보낸다.
일단 일반적으로 리액트에선 모든 게 클라이언트 단에서 이루어진다. Next.js는 이러한 리액트 컴포넌트들을 서버에서 부분적으로 렌더링 되는 페이지로 만들어서 클라이언트 단에서 모든 것을 부담하지 않아도 되게끔 개선했다. 근데 이 방식의 단점은, 이렇게 서버 단에서 생성된 HTML에 결국 클라이언트 단에서 hydrate 작업을 진행해야 한다는 것이다. 즉 추가적인 자바스크립트 코드가 필요하다는 의미다.
서버사이드 렌더링은 정적으로도, 동적으로도 구현할 수 있다.
- 정적(static): 서버 단에서 서버 컴포넌트와 클라이언트 컴포넌트 모두 빌드 시에 미리 렌더링될 수 있다. 일단 요청에 따른 응답 결과를 캐싱해두고, 뒤이은 요청에는 그 캐싱해둔 결과를 재사용하는 방식이다. SSG, ISR 방식이 이에 해당한다.
- 동적(dynamic): 서버 컴포넌트와 클라이언트 컴포넌트가 매 요청 시 렌더링되며, 응답 결과는 캐싱되지 않는다. SSR 방식이 이에 해당한다.
언제나 그렇듯이 은탄환은 없으니, 상황에 따라 서버 컴포넌트와 클라이언트 컴포넌트 중 더 적합한 것을 사용해야 할 것이다. 어떤 상황에 어떤 것을 쓰는 게 좋을까? Next.js는 다음과 같이 권장한다. :
- data fetching이 필요한 경우 👉 서버 컴포넌트
- 백엔드 자원에 접근해야 하는 경우 👉 서버 컴포넌트
- 클라이언트에 드러내면 안 되는 민감한 정보가 있을 때 👉 서버 컴포넌트
- 자바스크립트 코드를 줄여야 할 때 👉 서버 컴포넌트
- click, change 리스너 등을 사용하여 대화형(상호작용) 컨텐츠를 구현하려는 경우 👉 클라이언트 컴포넌트
- '상태(state)'을 활용하는 경우 👉 클라이언트 컴포넌트
- 브라우저 상에서만 지원하는 API(예: local storage와 같은 웹 스토리지를 다루는 API)를 사용하는 경우 👉 클라이언트 컴포넌트
Next.js에 따르면 우선 위와 같은 기준을 두고, 가능한 경우에는 서버 컴포넌트로 만들고 따로 클라이언트 컴포넌트로 구현이 필요한 부분들만 추출하는 편이 좋다고 한다.
Next 13 - Server and Client components
https://nextjs.org/blog/next-13
'학습 내용 > Front-End' 카테고리의 다른 글
vite + yarn berry(pnp) 사용할 때 node_modules가 생긴다!? (2) | 2023.01.31 |
---|---|
패키지 매니저 npm에서 yarn berry(zero install)로 바꾸기 (3) | 2023.01.24 |
intermock을 사용하여 mock data 만들기 (0) | 2022.07.10 |
[Next.js] Next.js의 프리 렌더링(pre-rendering) 옵션 3가지 / SSG, SSR, ISR (0) | 2022.06.01 |
Luhn algorithm(룬 알고리즘)으로 유효한 카드 번호인지 확인하기 (0) | 2022.05.10 |