client side에서의 fetch
리액트와 같은 client side에서는, fecth를 사용하여 값을 받아와 렌더링한다.
"use client"
export default function Page(){
const [isLoading, setIsLoading] = useState(true); // 추가
const [data, setData] = useState();
const getMovies = asynch () => {
const response = await fetch("url");
const json = await response.json();
setData(json);
setIsLoading(false);
}
useEffect(() => {
getMovies();
}, []);
return <div>{isLoading ? 'loading 중입니다.' : data}</div>
}
이 방식에는 다음의 문제점이 있다.
- useState로 로딩 화면을 제공해주기 위한 상태를 관리해야 한다. (안하면, 사용자는 빈 화면을 보고 있게 된다.)
- 브라우저가 서버로부터 데이터를 받아오는 시점의 로딩 시간에는 사용자가 빈 화면을 보게된다. (client side rendering의 문제)
- 브라우저에서, API 요청을 보내기 때문에 비밀값을 유지하기 힘들다.
서버에서, API 요청을 통해 값을 받아와서, 화면을 렌더링하고 완성된 HTML을 전달한다면, 이런 복잡한 과정을 거칠 필요가 없다!
Server side에서의 fetch
NextJS는 서버로서 동작하기 때문에, API 요청으로 값을 받아와서 렌디링하는 것이 가능하다.
const URL = "url"
async function getMovies(){
const response = await fetch(URL);
const json = await response.json();
return json;
}
export default async function Page(){
const movies = await getMovies();
return <div>{JSON.stringify(movies)}</div>
}
- API 요청을 위한 함수를 만들어주고, 이를 컴포넌트에서도 사용하기 위해 컴포넌트도 async 로 만들어준다.
이렇게 작성해주기만 하면, NextJS는 데이터를 fetch한 후, 서버 컴포넌트를 렌더링하여 사용자에게 반환한다.
하지만, 브라우저에서의 로딩 상태를 사라지게할 수 있지만, 서버로부터 HTML을 받아오는 시간 (렌더링하는 시간)의 로딩 상태를 사라지지 않는다.
- 즉, 사용자는 빈 화면을 보고 있게 된다.
이 문제는 NextJS의 Loading Component를 통해 해결이 가능하다.
Loading Component
NextJS에서는, loading.tsx 라는 이름의 특별한 컴포넌트가 존재하고, 이를 페이지 폴더에 만들어주면 로딩 시간에 해당 화면을 사용자에게 보여준다.
- 주의할 점은 해당 컴포넌트는 해당 폴더에서만 동작하고 하위 폴더들에는 적용되지 않는다.
이 기능이 가능한 이유는 NextJS가 streaming이 가능하기 때문이다.
NextJS는 페이지를 일부분씩 나눠서 사용자에게 보내는데, 가장 첫 번째로 보내는 것은 layout.tsx, 그 다음에는 loading.tsx를 전달한다.
- 특히 이 loading.tsx를 화면에 렌더링한 상태에서도, 브라우저 상단의 원이 돌아가는 것을 볼 수 있는데 브라우저가 웹 사이트 수신을 완료되지 않음을 알 수 있다.
결과적으로, NextJS는 페이지들을 작은 HTML 부분으로 나누어서 준비된 HTML 부분을 미리 브라우저에게 전달할 수 있다.
그리고 브라우저에게 아직 페이지가 렌더링 중이다, 혹은 완료되지 않았기 때문에 기다려줘야 한다고 알려준다.
- 이를 통해 사용자는 빈 화면이 아닌 기다려야 한다는 의미의 화면을 볼 수 있고, 데이터를 서버에서 fetch하기 때문에 비밀값도 유지해줄 수 있다.
Parallel Request
하나의 페이지에서 여러 fetch가 존재한다면, 이 fetch들을 병렬로 처리하는 것이 빠를 것이다.
- A 이후 B 요청을 하면 오랜 시간이 걸린다. 또한, A 요청의 대부분의 시간이 응답을 기다리는 시간이기 때문이다.
// 순차적으로 실행됨
const movie = await getMovie(id);
const videos = await getVideos(id);
// 병렬로 실행됨
const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
- Promise.all()을 통해 병렬로 처리할 수 있고, 결과를 배열로 반환한다.
이렇게 하면 함수를 순차적으로 실행하지 않고, 병렬적으로 실행되어 두 함수가 동시에 실행된다.
Suspense 태그
하지만 여전히 fetch 작업으로 인해, 해당 작업을 제외한 다른 UI 부분들을 사용자가 제공받지 못해 빈 화면을 보고 있다는 문제가 있다.
또한 하나의 작업이 먼저 완료되더라도, 여전히 다른 작업이 진행중이라면 기다려야 한다.
NextJS에서는 기다릴 필요 없는 UI를 먼저 렌더링하고, 그릴 준비가 완료된 UI를 먼저 렌더링하는 등의 작업이 가능하다.
이 방식은 loading.tsx와 같은 방식으로 streaming을 통해 작업이 완료된 UI를 바로바로 사용자에게 보내줄 수 있다.
fetch가 필요한 컴포넌트들을 아래와 같이 분리하여 작성해준다.
// movie-videos.tsx
async function getVideos(){
// 작업 ...
}
export default async function MovieVideos({id} : {id:string}){
const videos = await getVideos(id);
return <div>JSON.stringify(videos)<div>
}
// movie-data.tsx
async function getMovie(){
// 작업 ...
}
export default async function MovieInfo({id} : {id:string}){
const movie = await getMovie(id);
return <div>JSON.stringify(movie)<div>
}
이렇게 나눠서 작성함으로써 각각의 컴포넌트들을 따로따로 렌더링해줄 수 있게 된다.
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>}>
<MovieVideos id={id} />
</Suspense>
<div>Let's go</div>
</div>
}
각각의 컴포넌트가 async이기 때문에, <Suspense></Suspense> 로 감싸 렌더링을 기다린다.
- 이는 async 함수를 기다리기 위해 await을 사용하는 것과 동일한 원리이며, react에 포함된 태그이다.
또한, Suspense 태그는 해당 컴포넌트의 렌더링을 기다리는 동안 사용자에게 대체 UI를 렌더링할 수 있다.
- 이는 fallback 속성에 값을 전달함으로써 제공할 수 있다.
각각의 컴포넌트들은 기다리는 동안에는 fallback에 정의한 내용을 사용자에게 렌더링해주고, 요청이 완료되면 개별적으로 완료된 UI를 사용자에게 보여줄 수 있다.
Error Handling
NextJS에서는 예외를 핸들링하는 방식도 제공한다.
NextJS에서 아래와 같이 에러가 발생하면, 페이지를 렌더링할 수 없고 사용자는 아무런 화면도 볼 수 없다.
// movie-videos.tsx
async function getVideos(){
// 작업 ...
throw new Error('something broke...')
}
export default async function MovieVideos({id} : {id:string}){
const videos = await getVideos(id);
return <div>JSON.stringify(videos)<div>
}
NextJS는 에러를 핸들링하기 위해 error.tsx를 제공하는데, 해당 폴더의 페이지에서 에러가 발생하면, 해당 페이지를 사용자에게 제공해준다.
- 주의할 점은 CSR 컴포넌트로 만들어주어야 한다.
"use client"
export default function Error(){
return <h1>lol something broken...</h1>
}
- 이때, layout과 같이 정상적인 요소들도 같이 렌더링될 수 있다.
출처
노마드 코더님의 강의를 보고 작성했습니다.
'NextJS' 카테고리의 다른 글
2025년 01월 13일 (0) | 2025.01.13 |
---|---|
[ NextJS ] 기초, NextJS에서 경로를 만들고 라우팅하는 방법 (0) | 2025.01.13 |