page Router에서 /api Routes와 비슷한 개념이다. Web Request와 Response API를 사용해 특정 route에 대해 Custom Handler를 작성할 수 있게 해준다.
Route Handler는 app 폴더 안에 route.js로 정의한다. page나 layout 처럼 중첩이 가능하지만 같은 segment 레벨 내에 page.js를 둘 수 없다. 지원하는 HTTP Methods로는 GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS 가 있다.
GET 메소드와 Response 객체를 함께 사용할때 Static Route 라고 한다.
import { NextResponse } from 'next/server';
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY,
},
});
const data = await res.json();
// Response.json()도 가능한데 이렇게하면 Type 오류가 난다고 한다.
return NextResponse.json({ data });
}
아래의 경우를 Dynamic Route 라고 한다.
Client Side & Server Side 에서 컴포넌트를 렌더링 하는 것 외에도 Next.js는 서버에서 Static
과 Dynamic
렌더링을 통해 최적화 할 수 있는 옵션을 제공한다.
(Static 이 기본으로 사용되기 때문에 Dynamic은 다음에 정리하려한다.)
Server 와 Client Component는 빌드 타임에 pre-render 된다. 동작의 결과는 캐시되고 추가 요청에 재사용되어 성능상 효율적이다.( 물론 캐시는 무효화 할 수 있다.) 기존의 SSG, ISR 과 같다.
layout 이나 page 에서 동적 기능이나 동적 데이터 Fetching을 통해 동적 렌더링을 사용할 수 잇다. 이렇게 하면 Next.js는 요청 시간에 전체 route를 동적으로 렌더링 한다.
Page Router에서 사용했던 getServerSideProps
와 getStaticProps
가 이제는 fetch()
하나로 처리가 가능해졌다.
아래와 같은 이점들로 Server Component에서 데이터를 Fetch 하는 것을 권장한다.
물론 Client Component에서도 가능하며 SWR이나 React Query 사용을 추천한다. App Router 에서는 layout과 page, component 모두에서 data를 Fetch 할 수 있고 Streaming 과 Suspense 또한 호환이 가능하다.
다만, Layout은 부모 자식 간 데이터 전달이 불가능하다. Layout에서 data fetch를 그냥 사용하고, 중복된 데이터 요청은 알아서 캐시하고 중복요청은 제거해주니 큰 이슈는 없어보인다.
데이터에는 두 가지가 종류가 있다.
Static Data
: 자주 변하지 않는 정적인 데이터Dynamic Data
: 자주 변화하고 갱신이 필요한 데이터next.js는 기본적으로 빌드타임에 fetch되어 각 요청마다 재사용된다. (Static Fetch) pages router 에서의 SSG이다. 하지만 개발자는 필요에 따라 revailidate 시킬 수 있다. (Dynamic Fetch)
fetch는 디폴트로 데이터를 계속 reFetch 하고 캐싱한다. force-cache가 Default 이다.
특정 주기마다 캐시된 데이터를 revalidate하려면 next.revalidate 옵션을 사용할 수 있다.
// 10초마다 데이터를 갱신한다.
fetch('https://~~~', {next:{revalidate:10}})
데이터를 요청할 때마다 갱신하고 싶으면 cache:'no-store' 옵션을 사용한다.
fetch('https://~~', {cache:'no-store'})
Data Fetching 패턴에는 두 가지가 있다.
Parallel
: route의 요청이 시작되고 동시에 데이터를 로드한다. 이는 client-server waterfall과 로드에 소요되는 총 시간을 줄인다. ( 권장 )Sequential
: route의 요청이 각각 따로 수행되고 waterfall을 만든다. 의존관계가 있는 fetch를 실행하는 경우 주로 사용한다.사용자 경험을 향상시키기 위해 Suspense Boundary를 추가해 렌더링 작업을 분할 하였다.
import { type Album } from './api';
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`);
return res.json();
}
async function getArtistAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`);
return res.json();
}
export default async function Page({
params: { username },
}: {
params: { username: string };
}) {
// 두 요청을 병렬로 초기화한다. (Parallel)
const artistData = getArtist(username);
const albumData = getArtistAlbums(username);
// 먼저 artist의 promise가 resolve되기를 기다린다.
const artist = await artistData;
return (
<>
<h1>{artist.name}</h1>
{/* 먼저 artist 정보를 전송하고
albums는 susnpense boundary로 감싼다.
이렇게 렌더링 작업을 분할한다. */}
<Suspense fallback={<div>Loading...</div>}>
<Albums promise={albumData} />
</Suspense>
</>
);
}
// Albums Component
async function Albums({ promise }: { promise: Promise<Album[]> }) {
// album promise가 resolve되기를 기다린다.
const albums = await promise;
return (
<ul>
{albums.map((album) => (
<li key={album.id}>{album.name}</li>
))}
</ul>
);
}
데이터를 순차적으로 가져오기 위해 필요한 컴포넌트에서 직접 fetch(). 즉, 부모 컴포넌트가 우선적으로 fetch() 된 이후 자식 컴포넌트 fetch()
// ...
async function Playlists({ artistID }: { artistID: string }) {
// Wait for the playlists
const playlists = await getArtistPlaylists(artistID);
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
);
}
export default async function Page({
params: { username },
}: {
params: { username: string };
}) {
// Wait for the artist
const artist = await getArtist(username);
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
);
}
트리 내 다수의 컴포넌트에서 동일한 input을 가지는 fetch() 요청 시 Next.js는 중복된 요청을 제거한다.
참고
Route Handlers
Randering
Fetching, Caching
ServerAction
Fetching Pattern
Middleware
@asdf99245_AppRouter
@asdf99245_MiddleWare