[NextJS14] Data Fetching

foresec·2024년 1월 22일

NextJS

목록 보기
4/6

1. Client Side

"use client";

import { useEffect, useState } from "react";

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

이와 같이 CSR을 할 수 있으나 로딩상태를 별도로 관리한다거나, 보안문제, 메타데이터에 대한 정보를 삭제해야한다는 단점이 있음


2. Server Side

export const metadata = {
  title: "Home",
};

const URL = "https://nomad-movies.nomadcoders.workers.dev/movies";

async function getMovies() {
  const response = await fetch(URL);
  const json = await response.json();
  return json;
}

export default async function HomePage() {
	const movies = await getMovies()
  return <div>{JSON.stringify(movies)}</div>;
}
  • useState, useEffect 등의 리액트 hook이 하나도 쓰이지 않음

  • nextjs가 fetch로 해당 URL로부터 미리 가져온 정보를 제공함

  • getMovies는 전체가 user에게 전달되지 않으며 하단의 함수(const movies = await getMovies())도 user에게 전달되지 않음


3. Loading Components

같은 폴더 위상에 loading.tsx를 작성하는 것만으로 loading components가 해결됨

혹은 <Suspense>로도 가능

// app/(home)/page.tsx
export const metadata = {
  title: "Home",
};

const URL = "https://nomad-movies.nomadcoders.workers.dev/movies";

async function getMovies() {
  await new Promise((resolve) => setTimeout(resolve, 10000));
  const response = await fetch(URL);
  const json = await response.json();
  return json;
}

export default async function HomePage() {
  const movies = await getMovies();

  return <div>{JSON.stringify(movies)}</div>;
}

위 코드의 경우 setTimeout으로 인해 10초뒤 fetch가 수행되는데

// app/(home)/loading.tsx
export default function Loading() {
  return <h2>Loading...</h2>;
}
  • 기다리는 동안 다른 이 컴포넌트를 제외한 설정해놓은 다른 컴포넌트(cf. 네비게이션 등)들은 보여주고, data를 기다리는 부분에서는 loading.tsx가 노출됨

  • nextjs에서는 streaming이 일어남
    nextjs가 page를 나누고 처음엔 이미 준비되어있는 HTML 부분(cf. 네비게이션, 로딩 등)들만 브라우저로 보냄 -> BE에서 덜 끝낸 부분이 준비될때마다 브라우저로 보내고 끝나면 loading이 대체됨


4. Parallel Requests

여러개의 fetch를 동시에 시작하려면 어떻게 해야할까

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

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

async function getVideos(id: string) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  return response.json();
}

export default async function MovieDetail({
  params: { id },
}: {
  params: { id: string };
}) {
  const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);

  return (
    <>
      <h1>{movie.title}</h1>
    </>
  );
}

이와 같은 경우 순차적으로 getMovie->getVideos순으로 실행되는 것이 아닌, Promise객체를 활용하여 동시에 fetch가 시작됨


5. Suspense

각각의 함수의 데이터 도착하기전 loading상태를 따로 관리하고 싶다면

이와 같이 함수들을 각 컴포넌트로 분리하고

// components/movie-info.tsx
import { API_URL } from "../app/(home)/page";

async function getMovie(id: string) {
  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) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  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>;
}

각 컴포넌트를 import 한 뒤

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

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>
  );
}
  • Suspense태그fallback을 활용하여 로딩관리
    결과적으로 fallback에 입력한 값에서 약 2초 뒤 movie videos가, 그뒤로 3초(총 5초) 뒤 movie info가 렌더링됨

6. Error Handling

error.tsx를 활용(해당 폴더에서)하여 error가 발생했을 때 오류로 아무것도 보여주지 않는 것 보다는, 현재 페이지에 문제가 있음을 알리고 해당기능을 제외한 기능을 사용할 수 있도록 함(cf 홈으로 돌아가기, 네비게이션 바 이용하기)

// (movies)/movies/[id]/error.tsx
"use client";
export default function ErrorOMG() {
  return <h1>something broken...</h1>;
}
profile
왼쪽 태그보다 시리즈 위주로 구분

0개의 댓글