"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을 할 수 있으나 로딩상태를 별도로 관리한다거나, 보안문제, 메타데이터에 대한 정보를 삭제해야한다는 단점이 있음
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에게 전달되지 않음
같은 폴더 위상에 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이 대체됨
여러개의 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가 시작됨
각각의 함수의 데이터 도착하기전 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을 활용하여 로딩관리error.tsx를 활용(해당 폴더에서)하여 error가 발생했을 때 오류로 아무것도 보여주지 않는 것 보다는, 현재 페이지에 문제가 있음을 알리고 해당기능을 제외한 기능을 사용할 수 있도록 함(cf 홈으로 돌아가기, 네비게이션 바 이용하기)
// (movies)/movies/[id]/error.tsx
"use client";
export default function ErrorOMG() {
return <h1>something broken...</h1>;
}