Next
기존 fetching data
- 기존의 데이터 페칭은 클라이언트에서 일어난다.
- 가져온 데이터로
useState
를 설정하고, 설정된 데이터를 활용하여야 하며 이러한 작업은 클라이언트 컴포넌트, 즉 브라우저가 API 요청을 보내는 것을 기반으로 하며, 브라우저에서 보내는 API 요청은 필연적으로Loading
상태를 사용자가 인지할 수 있도록 추가적인 작업이 필요하다. - 또한 브라우저에서 데이터 요청을 보내기 때문에 네트워크 탭에서 API KEY에 대한 접근이 쉬워지고, 보안 측면에서도 안전할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"use client";
import { useState, useEffect } from "react";
export default function Example() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
const getMovie = async () => {
const res = await fetch("https://.../movieUrl");
const json = await res.json();
setMovies(json);
setLoading(false);
};
useEffect(() => {
getMovie();
}, []);
return <div>{loading ? "Loading..." : JSON.stringfy(movies)}</div>;
}
server fetching
- 서버 측에서 데이터를 가져오는 작업을 실행 후 사용자에게 보여주기만 한다면 이 모든 작업을 줄일 수 있다.
- 서버에서 데이터를 페칭 후, 결과를 사용자에게 보여주기 떄문에 이 모든 작업이 진행되는
loading
상태와 데이터를 설정하기 전까지의state
, 상태값이 변함에 따라 설정하는setState
, 이 모든 작업동안 발생하는rendering
등의 모든 작업을 단축시킬 수 있다. - 또한 이 작업은 캐싱되기 떄문에
Next.js
가 캐싱된 데이터를 기억하고 또 다시fetching
발생하지 않는다. - 위의 과정동안 브라우저에서 발생한 것은 없기 떄문에 네트워크 탭에서도 API 요청이 발생하지 않게 된다.
- 컴포넌트가 async로 작성되어야 하는 이유는, Next.js가 페이지를 작은 HTML로 나누어 완료된 부분을 사용자에게 보여주게 되고, 데이터 페칭이 완료된 후 보여주어야 하는 컴포넌트가 아직 준비되지 않았다는 것을 알려주는 것이다.
1
2
3
4
5
6
7
8
9
10
11
const URL = "http://.../movieUrl";
async function getMovie() {
return await fetch(URL).json();
}
export default async function Example() {
const movies = getMovie();
return <div>{JSON.stringfy(movies)}</div>;
}
loading
- 서버 컴포넌트에서 실행하는 데이터 페칭이 극단적으로 느려진다면 어떻게 될까
- 서버에서 실행한 데이터 페칭이 완료되기 전까지 사용자에게는 어떠한 UI도 주어지지 않는다.
- 만약 데이터를 가져오기까지 5초가 걸린다고 한다면 5초동안 사용자는 빈 화면에서 탭의 로딩 상태를 나타내는 스피너만 보고 있어야 한다.
- 이러한
UX
를 방지하고자Next.js
에서는 특별한 기능을 사용할 수 있다. - loading 명을 가진 파일은 서버가 실행되는 동안의(loading 상태) 보여질 페이지로 인식한다.
- 이렇게 작성된 loading 파일은 서버가 실행되는 동안
<div>데이터를 가져오고 있습니다.</div>
를 보여주게 되고, fetching이 끝난 이후에는 Example.tsx 를 정상적으로 보여주게 되기에 사용자 경험을 해치지 않을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
// page.tsx => 위의 Example.tsx
async function getMovie() {
// 데이터 fetch가 발생하기 까지의 5초 설정
await new Promise((resolve) => setTimeout(resolve, 5000));
return await fetch(URL).json();
}
// page.tsx가 있는 디렉토리의 loading.tsx
export default function Loading() {
return <div>데이터를 가져오고 있습니다.</div>;
}
최적화 시키기
getMovie
와 같이 다른 데이터 페칭이 동시에 일어나는 경우에는 어떻게 할까async await
를 사용하고 있기 때문에 직렬적으로 작성하면 앞의 fetching이 필수적으로 끝나야만 뒤의 fetching이 실행된다.- 이를 병렬적으로 사용하기 위해 await Promise.all 을 사용하여 동시에 작업을 처리할 수 있다.
- 하지만 이 중 하나의 요청만이라도 에러가 발생하게 되면
catch
로 빠져버리기 떄문에 이 또한 좋은 방법이 아니다. - 또한 모든 비동기 요청이 완료되어야만
resolve
를 반환한다.
- 하지만 이 중 하나의 요청만이라도 에러가 발생하게 되면
- Movie 컴포넌트에서 movie와 video fetching을 전부 실행하는 것이 아닌, 데이터를 보여주는 역할만 할 수 있도록 독립적으로 분리할 수 있다.
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
// Promise all 을 통한 동시 처리
// page.tsx
export const API_URL = "http://.../movie";
const getMovie = async (id: string) => {
return await fetch(API_URL).json();
};
const getVideo = async (id: string) => {
return await fetch(`${API_URL}/${id}`).json();
};
export default async function Movie({
params: { id },
}: {
params: { id: string },
}) {
const [movie, video] = await Promise.all([getMovie(id), getVideo(id)]);
return (
<div>
<div>{movie.title}</div>
<div>{video}</div>
</div>
);
}
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
// fetching 분리
// movie-info.tsx
async function getMovie(id: string){
...
}
export default async function MovieInfo({id}: {id: string}){
const movie = getMovie(id);
return <div>{JSON.stringfy(movie)}</div>
}
// movie-video.tsx
async function getVideo(id: string){
...
}
export default async function MovieVideo({id}: {id:string}){
const video = getVideo(id);
return <div>{JSON.stringfy(video)}</div>
}
// page.tsx
export default async function Movie({params: {id}}: {params:{id:string}}){
return (
<div>
<Suspense fallback={<h6>Movie info loading...</h6>}><MovieInfo id={id}/></Suspense>
<Suspense fallback={<h6>Movie video loading...</h6>}><MovieVideo id={id}/></Suspense>
</div>
)
}
- 이처럼
data fetching
이 발생하는 컴포넌트와 분리하여 독립적인 컴포넌트로 사용 시,data fetching
을 기다리지 않아도 되며, 모든 작업이 완료되는 동안 사용자에게 보여줄 다른 컴포넌트를 작성하는 등의 역할이 가능하다.
Error
- 만약 위와 같은 데이터 요청에 대한 에러가 발생하면 어떻게 될까
- 전체 페이지의 동작이 멈추게 되며 사용자는 어떤 일이 발생하고 있는 지 알 수 없다.
- 이를 방지하기 위해 특정 페이지에서 에러가 발생 시 최소한의 네비게이션 혹은 다른 페이지를 보여주기 위해서, 어떠한 화면을 보여줄 것인지를
error
파일로 작성할 수 있다. - 에러 페이지에 대한 파일은 각 페이지 폴더 내부에 작성해야 하며, 컴포넌트명은 바뀌더라도 파일명은
error
를 유지해야 한다. - 이렇게 작성된 파일에 대해서 프레임워크가 인식하여 에러 페이지를 내보내게 된다.
- 페이지에 대한 레이아웃은 유지된다.
- 클라이언트 컴포넌트로 작성되어야 하기 때문에 use client를 작성해야 한다.
1
2
3
4
5
6
// /home/error.tsx
"use client";
export default function ErrExample() {
return <div>서버 에러입니다.</div>;
}