티스토리 뷰

개발공부/⬛ Next.js

[Next.js 14] Parallel Requests

2024. 7. 10. 13:17

🚩 이 글은 노마드코더의 Next.js 시작하기 강의를 보고 정리한 글입니다. 

 

 

영화소개 상세페이지를 만든다고 했을 때,

movies/[id]/page.tsx에서 상세정보를 가져오기 위해 API를 2번 요청해야한다고 해보자.

 

 

아래와 각각의 정보를 fetch하는 함수를 만들고 실행해준다면 getMovie()와 getVideos()가 순차적으로 실행된다.

getMovie() 함수의 실행이 완료되어야만 getVideos()가 실행된다는 것이다.

아래 코드와 같은 경우 최소 getMovie()에서 5초, 그 다음으로 getVideos()에서 5초가 지연되어 최소 10초의 시간이 소요된다.

// movies/[id]/page.tsx

async function getMovie(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);
    
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
}

async function getVideos(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);

    await new Promise((resolve) => setTimeout(resolve, 5000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
}

export default async function Movies({ params: { id } }: { params: { id: string } }) {
    const movie = await getMovie(id);
    const videos = await getVideos(id);

    return <h2>{movie.title}</h2>;
}

 

 

 

이럴 경우, Promise.all()을 사용하면 병렬적으로 data를 fetch할 수 있다. 

Promise.all을 사용하면 getMovie(), getVideos()가 동시에 실행되고 모든 함수의 실행이 끝나야 결과를 받을 수 있다.

그렇다면 아래 코드의 경우 최소 5초의 시간이 소요된다. 그동안은 ui를 볼 수 없다.

// movies/[id]/page.tsx

async function getMovie(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);

    await new Promise((resolve) => setTimeout(resolve, 5000));

    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
}

async function getVideos(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);

    await new Promise((resolve) => setTimeout(resolve, 5000));

    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
}

export default async function Movies({ params: { id } }: { params: { id: string } }) {
    console.log("start fetching");
    const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
    console.log("end fetching");
    return <h2>{movie.title}</h2>;
}

 

 

 

 

 

 

streaming을 사용하면 getMovie(), getVideos() 중 먼저 데이터가 준비된 ui는 다른 함수가 완료되는걸 기다리지 않아도 먼저 보여줄 수 있다.

 

기존에는 movies/[id]/page.tsx에서 2개의 fetch를 진행했지만 새로운 2개의 컴포넌트 movie-info.tsx, movie-videos.tsx를 만들고 각 fetch함수를 각각의 컴포넌트 파일에 옮겨준다. 그러면 각 컴포넌트는 자신에 관한 data만 fetch한다.

// components/movie-info.tsx

import { API_URL } from "../app/(home)/page";

async function getMovie(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);

    await new Promise((resolve) => setTimeout(resolve, 5000));

    const response = await fetch(`${API_URL}/${id}`);
    return response.json();
}

export default async function MovieInfo({ id }: { id: string }) {
    const movie = await getMovie(id);
    return <h6>{JSON.stringify(movie)}</h6>;
}
// components/movie-videos.tsx

import { API_URL } from "../app/(home)/page";

async function getVideos(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);

    await new Promise((resolve) => setTimeout(resolve, 3000));

    const response = await fetch(`${API_URL}/${id}/videos`);
    return response.json();
}

export default async function MovieVideos({ id }: { id: string }) {
    const videos = await getVideos(id);
    return <h6>{JSON.stringify(videos)}</h6>;
}

 

이렇게 하면 각각의 컴포넌트에서 data를 개별적으로 기다리도록 분리되었다.

 

 

그리고 기존의 movies/[id]/page.tsx에서 Suspense로 각 컴폰너트를 감싸준다.

이 Suspense는 Next.js의 고유기능이 아니라 React의 기능이다.

// movies/[id]/page.tsx

import { Suspense } from "react";
import MovieInfo from "../../../../components/movie-info";
import MovieVideos from "../../../../components/movie-videos";

export default async function Movies({ 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>
    );
}

 

 

Suspense는 fallback이라는 props를 가지고 있는데 이 속성은 컴포넌트가 await되는 동안 표시할 메세지를 render할 수 있게 해준다.

 

Suspense의 역할은 데이터를 fetch하기 위해 자신이 감싸고 있는 컴포넌트()를 await하고 데이터를 fetching하는 동안에는 fallback에 있는 컴포넌트를 보여준다. 그리고 데이터 fetching이 완료되면 완성된 컴포넌트를 보여준다.

 

이렇게 Suspense를 활용하면 각 컴포넌트 별로 병렬적인 data fetching이 가능함과 동시에 요청이 완료된 컴포넌트는 다른 컴포넌트의 완료여부를 기다릴 필요 없이 즉시 렌더된다.

 

 

 

이 경우 movies/[id]/page.tsx에서는 데이터 fetching이 일어나지 않아 movies/[id]/loading.tsx은 보여지지 않는다.

 

이전에 page.tsx에서 fetching을 진행할 때는 loading.tsx 컴포넌트가 전체 페이지를 대체하고 모든 fetching이 마무리되어야 페이지가 표시되었다.

 

하지만 컴포넌트를 분리하고 Suspense를 사용하면 페이지는 즉시 표시되고 fetch가 필요한 컴포넌트만 각각의 로딩 상태를 가졌다가 렌더된다.

 

 

 

 

물론 이 모든 방법은 어떤 것이 정답인 것이 아니라 각각 필요한 상황에 맞는 방법을 선택해서 사용하면 된다.

반응형
프로필사진
개발자 삐롱히

프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그