CRS에서 useState를 사용하면 개발자 도구에서 요청 url이 그대로 노출되기 때문에 보안적 문제 때문에 데이터베이스는 client가 아닌 server쪽에서 접근해야했다. (중간 매개체가 필요했다.)
↔️ 하지만 최신 버전의 NextJS와 server component가 있다면, API가 필요 없다. (중간 매개체가 필요 없다.)
Server component에서 fetch를 요청할 때는 Client Component에서 fetch를 요청할 때보다 단순하다. (코드가 줄어든다.)
async function getMovies() {
const reponse = await fetch(URL);
const json = await reponse.json();
return json;
}
async function getMovies() {
return fetch(URL).then(response => response.json());
}
seState
와 useEffect
사용이 사라졌다.async
를 사용하는 이유? ➡️ await
를 사용하기 위해서
await
는? ➡️ 어떤 일이 발생하기를 기다리려고 await
를 사용한다.
HomePage 함수 앞에 async
가 붙고, movies라는 변수에 await
가 사용되었다.
async function getMovies() {
return fetch(URL).then(response => response.json());
}
export default async function HomePage() {
const movies = await getMovies();
return (
<div>
{JSON.stringify(movies)}
</div>
);
}
상단에 로딩 바는 백엔드에서 로딩이 발생하고 있다는 의미이다.
➡️ 사용자 입장에서는 백엔드에서 로딩이 발생하고 있더라도 UI적으로 반응이 바로 와야한다.
async function getMovies() {
await new Promise((resolve) => setTimeout(resolve,5000)); // 억지로 5초 로딩
console.log("I'm fetching!");
return fetch(URL).then(response => response.json());
}
loading.tsx
파일을 만들면, 백엔드 측에서 로딩을 해야할 때 임시로 사용자에게 해당 페이지UI를 제공한다.
이름은 반드시 loading
이어야 한다!
해당 폴더 내에서만 작동한다.
export default function Loading() {
return <h2>Loading...</h2>;
}
fetch해야하는 요청이 두 가지인데, 각각 따로 await를 요청하면 순차처리(직렬요청)이 되기 때문에 시간이 오래걸린다.
이를 해결하기 위해 Promise.all
을 사용한다.
import { API_URL } from "@/app/(home)/page";
async function getMovie(id:string) {
console.log(`Fetching movies: ${Date.now()}`)
await new Promise((resolve) => setTimeout(resolve, 2000));
const response = await fetch(`${API_URL}/${id}`);
return response.json();
}
async function getVideos(id:string) {
console.log(`Fetching videos: ${Date.now()}`)
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} // [id] 에서 id를 가져온다.
}: {params:{id:string};
}) {
console.log('start fetching');
const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
console.log('end fetching');
return <h1>{movie.title}</h1>;
}
const movie = await getMovie(id);
const videos = await getVideos(id);
⬇️
const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
movie와 videos를 동시 요청하지만, 먼저 데이터가 받아지는 쪽을 먼저 UI에 반영하도록 할 때 사용한다.
➡️ 동시 요청할 부분을 두 개의 component로 나눈다.
이 각 component는 자신에 관한 데이터만 fetch했고,
Suspense component
가 데이터를 fetch하기 위해 이 안의 component들을 await
한다.
suspense component
에는 fallback
이라는 prop
이 있고,
fallback prop은 component가 await되는 동안 표시할 메시지를 render할 수 있게 해준다.
page.tsx에서 data fetching을 요청하지 않기 때문에 loading.tsx은 아무것도 하지 않는다.
// ====================page.tsx==================== \\
export default async function MovieDetail({
params: {id} // [id] 에서 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>
);
}
// ====================movie-videos.tsx==================== \\
import { API_URL } from "@/app/(home)/page";
async function getVideos(id:string) {
console.log(`Fetching videos: ${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>
}
fetch
하기 위해서는 해당 function은 async
가 되어야 한다.await
를 이용해서 fetch 한다.Promise.all
을 사용해서 병렬 요청 한다.Suspense Component
를 사용하여 로딩상태를 분리한다.➡️ 해야할 건 단순하다. Component를 async 하고 해당 Component를 Suspense로 감싸준다!
error.tsx
만 만들어 놓으면 해당 페이지 혹은 자식 페이지에 에러가 있을 때 자동으로 해당 error.tsx UI가 반영된다.
에러난 부분만 반영되고 나머지는 정상적으로 보인다.
단, error Component에는 반드시 use client
를 사용해야 한다.
"use client";
export default function ErrorMOG() {
return <h1>lol something is broken,,,!</h1>
}