Next.js 공부하기 5일차
안녕하세요 🐸
오늘은 Next.js의 data fetch 방법에 대해 공부했습니다
data fetch
Next.js에서 data fetch 하는 방법을 정리해 보겠습니다.
- 클라이언트 컴포넌트에서 data fetch
- 서버 컴포넌트에서 data fetch
- loading component 만들기
- 병렬 요청 하기
- Suspense 사용하여 병렬 요청 분리하여 사용하기
클라이언트 컴포넌트에서 data fetch 하기
클라이언트 컴포넌트에서 data fetch 하는 방법은 리액트의 문법과 매우 유사합니다.
단, 몇 가지 주의할 점이 있습니다.
useState()
등의 기능은"use client"
를 명시한 모듈에서만 사용 가능- 클라이언트 컴포넌트에서
metadata
는 사용 불가
몇 가지 단점도 있습니다.
- metadata 사용 불가
- 컴포넌트의 로딩 상태를 직접 관리
- DB 연결, API 키를 사용한 연결 등의 작업은 보안 취약성으로 인해 작업 불가
useState()
등을 사용함으로 인해 이를 관리하는 부수 작업이 과다- 개발자도구에서 네트워크를 통하는 작업이 그대로 노출
클라이언트 컴포넌트에서 data fetch 하는 예시)
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
"use client"
import { useEffect, useState } from "react"
export default function Tomato(){
const [isLoading,setIsLoading] = useState(true);
const [movies, setMovies] = useState();
const getMovies = async() =>{
const response = await fetch("https://nomad-movies.nomadcoders.workers.dev/movies");
const json = await response.json();
setMovies(json);
setIsLoading(false);
}
useEffect(()=>{
getMovies();
},[])
return (
<div>
{isLoading? "Loading..." : JSON.stringify(movies)}
</div>
)
}
리액트의 방법과 동일한 부분이 많아 부연 설명은 하지 않겠습니다.
서버 컴포넌트에서 datafetch 하기
서버 컴포넌트에서 data fetch 했을 때의 장점으로
- 번거로운 관리작업의 불필요
- 네트워크 통신의 미노출
- 컴포넌트 로딩 상태 관리 불필요
- DB, API KEY 등의 중요 정보 노출 방지
- metadata 사용 가능
- 데이터 캐싱
서버 컴포넌트에서 data fetch 하는 과정을 요약하자면 다음과 같습니다.
- data fetch 작업을 수행할 async/await 함수 작성
- 화면을 그려줄 export default 함수를 async/await 로 변경
- data fetch 작업을 수행할 함수 호출
단점은 서버에서 작업이 오래걸린다면 사용자는 화면에 접근조차 못하고 아무런 UI를 볼 수 없다는 점 입니다.
서버 컴포넌트에서 data fetch로 변경한 예시)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 서버 컴포넌트에서는 metadata도 사용 가능
export const metadata = {
title : 'Home'
}
const url = "https://nomad-movies.nomadcoders.workers.dev/movies";
async function getMovies(){ // data fetch할 async/await 문법의 함수
const response = await fetch(url);
const json = await response.json();
return json;
}
// 화면을 그려줄 export default에도 async를 명시
export default async function HomePage(){
// 내부에서 async/await 함수를 통한 data fetch
const movies = await getMovies();
return (
<div>
{JSON.stringify(movies)}
</div>
)
}
useState, useEffect 와 같이 리액트 훅을 사용하여 관리해야하는 불필요한 작업이 없어집니다.
특히 장점이라고 생각하는 부분은 서버 컴포넌트에서의 통신은 한번만 이루어지고 캐싱하기 때문에 같은 페이지에 다시 접속했을 때 서버는 매번 통신을 반복하지 않는 다는 것 입니다.
Loading Components
서버의 작업이 길어져서 사용자가 아무런 UI로 접하지 못하고 기다리는 상황을 방지하기 위해 사용할 수 있습니다.
사용 방법
로딩 화면을 보여주고 싶은 URL 경로에 loading
파일을 생성합니다.
1
2
3
4
5
6
7
export default function Loading(){ // 함수의 이름은 다르게 지어도 무방
return (
<div>
<h2>Loading...</h2>
</div>
)
}
위와같이 loading
파일을 생성 한 후로는 페이지 접속 시 서버에서 작업이 길어져도 layout
파일과 함께 loading
이 제공되어 사용자는 UI를 제공 받아 현재 로딩 중 임을 확인할 수 있습니다.
강의에서는 서버에서 로딩하는 동안 파비콘이 스피너로 대체되는 것을 보여줬는데, 버전의 차이 때문인지 명확하지 않은 이유로 현재는 스피너 없이 바로 화면이 로딩됩니다.
병렬 요청
Promise.all()
을 사용하여 data fetch 을 병렬로 요청할 수 있습니다.
병렬 요청을 사용해야 하는 이유는 5초의 시간이 걸리는 작업이 10개 있다 가정할 때 순차적으로 수행하면 50초가 걸리는 참사가 생기기 때문입니다. 병렬로 처리한다면 시간을 크게 줄일 수 있겠죠.
예시)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const funcA = async() => {
// 생략
return response
}
const funcB = async() => {
// 생략
return response
}
export default async Page(){
// Promise.all()은 넣은 함수의 결과로 오는 프로미스를 순서대로 배열로 반환
const [A,B] = await Promise.all([funcA(), funcB()]);
return (
// 생략
)
}
Suspense
위에서 사용한 Promise.all()
을 사용한 병렬 요청은 한 가지 큰 단점이 있습니다.
결과로 받은 A를 화면에서 랜더링하기 위해 사용하고 싶을 때, B가 종료되지 않았다면 사용할 수 없습니다.
즉 Promise.all()
에 전달된 프로미스 전체가 끝나기 전까지는 그 중 하나의 요소만을 별개로 사용할 수 없다는 말 입니다.
사용 방법
- data fetch가 필요한 최소 단위를 컴포넌트로 분리
- 리액트의
<Suspense>
태그 안에 컴포넌트를 작성 <Suspense>
에fallback
작성 ( 컴포넌트가 로딩되는 동안 보여질 컴포넌트(혹은 문구))
사용 예시)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default async function MovieDetail({
params : { id }
}:{
params : { id : string };
}){
return(
<div>
<Suspense fallback={<h1>Loading movie info</h1>}>
<MovieInfo id={id} />
</Suspense>
<Suspense fallback={<h1>Loading movie videos</h1>}>
<MovieVidoes id={id} />
</Suspense>
</div>
)
}
기존에 영화 정보, 영화 동영상을 하나의 페이지에서 병렬로 작업했던 것을 각각의 컴포넌트에 작업을 위임하고 <Suspense>
태그로 감쌌습니다.
가장 큰 장점은 각각의 요청은 각각의 컴포넌트에 유효하기 때문에 먼저 data fetch 가 끝난 컴포넌트는 먼저 랜더링 될 수 있다는 것 입니다.