포스트

Next.js 공부하기 5일차

안녕하세요 🐸

오늘은 Next.js의 data fetch 방법에 대해 공부했습니다

data fetch

Next.js에서 data fetch 하는 방법을 정리해 보겠습니다.

  1. 클라이언트 컴포넌트에서 data fetch
  2. 서버 컴포넌트에서 data fetch
  3. loading component 만들기
  4. 병렬 요청 하기
  5. Suspense 사용하여 병렬 요청 분리하여 사용하기

클라이언트 컴포넌트에서 data fetch 하기

클라이언트 컴포넌트에서 data fetch 하는 방법은 리액트의 문법과 매우 유사합니다.
단, 몇 가지 주의할 점이 있습니다.

  1. useState() 등의 기능은 "use client" 를 명시한 모듈에서만 사용 가능
  2. 클라이언트 컴포넌트에서 metadata 는 사용 불가

몇 가지 단점도 있습니다.

  1. metadata 사용 불가
  2. 컴포넌트의 로딩 상태를 직접 관리
  3. DB 연결, API 키를 사용한 연결 등의 작업은 보안 취약성으로 인해 작업 불가
  4. useState() 등을 사용함으로 인해 이를 관리하는 부수 작업이 과다
  5. 개발자도구에서 네트워크를 통하는 작업이 그대로 노출

클라이언트 컴포넌트에서 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 했을 때의 장점으로

  1. 번거로운 관리작업의 불필요
  2. 네트워크 통신의 미노출
  3. 컴포넌트 로딩 상태 관리 불필요
  4. DB, API KEY 등의 중요 정보 노출 방지
  5. metadata 사용 가능
  6. 데이터 캐싱

서버 컴포넌트에서 data fetch 하는 과정을 요약하자면 다음과 같습니다.

  1. data fetch 작업을 수행할 async/await 함수 작성
  2. 화면을 그려줄 export default 함수를 async/await 로 변경
  3. 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() 에 전달된 프로미스 전체가 끝나기 전까지는 그 중 하나의 요소만을 별개로 사용할 수 없다는 말 입니다.

사용 방법

  1. data fetch가 필요한 최소 단위를 컴포넌트로 분리
  2. 리액트의 <Suspense> 태그 안에 컴포넌트를 작성
  3. <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 가 끝난 컴포넌트는 먼저 랜더링 될 수 있다는 것 입니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.