Next.js14 정리(1) 이어서…
Parallel Requests
import { Link } from "next/link"; export const metadata = { title: "Home", }; export const API_URL = "https://.../movies"; async function getMovies() { const response = await fetch(API_URL); const json = await response.json(); return json; } export default async function HomePage() { const movies = await getMovies(); return ( <div> {movies.map((movie) => ( <li key={movie.id}> <a href={`movies/${movie.id}`}>{movie.title}</a> </li> ))} </div> ); }
movies/[id]
여러 api 불러올 때 병렬 처리 Promise.all 함수로 불러올 수 있다.
단점 : 모든 api가 불러와져야 화면을 보여준다.
import { API_URL } from "../../../(home)/page"; async function getMovie(id) { await new Promise((resolve) => setTimeout(resolve, 1000)); const res = await fetch(`${API_URL}/${id}`); return res.json(); } async function getVideos(id) { await new Promise((resolve) => setTimeout(resolve, 1000)); const res = await fetch(`${API_URL}/${id}/videos`); return res.json(); } export default async function Movie({ params: { id } }) { const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]); return ( <div> <h1>{movie.title}</h1> </div> ); }
Suspense
두 api가 동시에 불러오지만 준비가 먼저 끝난 거는 먼저 보여준다.
⇒ 병렬적으로 2가지를 동시에 fetch할 수 있는데 하지만 하나의 요청이 완료되면 즉시 component가 render 된다.
⇒ 각각 api를 불러오는 컴포넌트로 만들어 개별적으로 기다리게 한다.
Suspense 가 데이터를 fetch 하기 위해 안의 컴포넌트를 await한다.
Suspense 의 fallback 컴포넌트가 await 되는 동안 (fetch 중에)표시할 메세지를 render할 수 있게 해준다.
⭐여기서 이제 데이터 패치를 하지 않기 때문에 ( 각 컴포넌트에서 데이터 패치 ) 형제 페이지인 loading.jsx는 활동하지 않음
//movies/[id] import { Suspense } from "react"; import MovieInfo from "../../../../components/movie-info"; import MovieVideo from "../../../../components/movie-videos"; export default async function Movie({ params: { id } }) { return ( <div> <Suspense fallback={<h1>Loading Info</h1>}> <MovieInfo id={id} /> </Suspense> <Suspense fallback={<h1>Loading video</h1>}> <MovieVideo id={id} /> </Suspense> </div> ); }
components/movie-info.jsx
import { API_URL } from "../app/(home)/page"; async function getMovie(id) { await new Promise((resolve) => setTimeout(resolve, 1000)); const res = await fetch(`${API_URL}/${id}`); return res.json(); } export default async function MovieInfo({ id }) { const movie = await getMovie(id); return ( <div> <h6>{JSON.stringify(movie)}</h6> </div> ); }
components/movie-videos.jsx
import { API_URL } from "../app/(home)/page"; async function getVideos(id) { await new Promise((resolve) => setTimeout(resolve, 1000)); const res = await fetch(`${API_URL}/${id}/videos`); return res.json(); } export default async function MovieVideo({ id }) { const videos = await getVideos(id); return ( <div> <h6>{JSON.stringify(videos)}</h6> </div> ); }
Error Handling
에러가 발생할 경우(ex, 데이터 패치 오류) 보여줄 페이지
movies/[id] 폴더에 error.jsx 파일 생성 ⇒ 해당하는 페이지 옆에 만들어야 한다.
error 컴포넌트에는 "use client" 넣어야 한다.
"use client"; export default function Error() { return ( <div> <h1>Error</h1> </div> ); }
Dynamic Metadata
async function 처럼 generateMetadata에 params를 받아 올 수 있어 동적 메타데이터를 적용 할 수 있다.
import { Suspense } from "react"; import MovieInfo, { getMovie } from "../../../../components/movie-info"; import MovieVideo from "../../../../components/movie-videos"; export async function generateMetadata({ params: { id } }) { const movie = await getMovie(id); return { title: movie.title, }; } export default async function Movie({ params: { id } }) { return ( <div> <h3>Detail</h3> <Suspense fallback={<h1>Loading Info</h1>}> <MovieInfo id={id} /> </Suspense> <Suspense fallback={<h1>Loading video</h1>}> <MovieVideo id={id} /> </Suspense> </div> ); }