Next.js 가 이번에 새롭게 버전이 올라가면서 많은것들이 바뀌게 되었다.
이젠 page directory 라우팅방식과 app directory 두가지를 사용 할 수 있으며 app directory 를 사용할경우 metadata API, nested layout,등을 사용 할 수 있습니다. 또한 모든 컴포넌트는 기본적으로 서버컴포넌트가 되며 클라이언트 컴포넌트로 변경하고싶은경우 해당 컴포넌트 맨 위에 "use client" 를 추가해야 합니다.
클라이언트 컴포넌트에 서버컴포넌트를 import 하는것은 제한된다.
'use client'
// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from './example-server-component'
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode
}) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExampleServerComponent />
</>
)
}
위 예시 코드는 왜 제한될까?
Nextjs 렌더링 플로우에서 Next.js 는 초기 페이지 로드 시 서버에서 모든 서버 컴포넌트를 사전 렌더링 하여 HTML 로 전송합니다.
이때 클라이언트 컴포넌트 는 건너뜁니다.
따라서 클라이언트 컴포넌트에서 서버 컴포넌트를 import 하여 사용한다면 서버 컴포넌트의 렌더링은 서버에서 수행 되지만, 클라이언트 컴포넌트의 대한 렌더링은 수행되지 않게 됩니다. 이를 극복하려면 불완전한 컴포넌트를 사용하거나 추가적인 작업비용이 소모됩니다.
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from './example-client-component'
import ExampleServerComponent from './example-server-component'
// Pages in Next.js are Server Components by default
export default function Page() {
return (
<ExampleClientComponent>
<ExampleServerComponent />
</ExampleClientComponent>
)
}
위 코드는 작동을 정상 작동을 하게됩니다.
안티패턴과의 차이점은 클라이언트 컴포넌트에 children prop 으로 서버컴포넌트를 넘겼습니다.
"ExampleClientComponent" 즉 클라이언트 컴포넌트는 children이 서버컴포넌트인지 클라이언트 컴포넌트 인지 알지 못합니다.
"ExampleClientComponent" 의 책임은 children 이 어디에 배치 될것인지를 결정하는것 뿐입니다.
이런 패턴으로 코드를 작성한다면 서버,클라이언트 컴포넌트는 렌더링을 독립적으로 수행하게 됩니다.
독립적으로 랜더링이 이루어진다면 어떤 이점이 있을까요?
- 렌더링이 독립적으로 이루어지므로 각각의 역할과 특성에 따라 최적화된 방식으로 처리가능
- 서버컴포넌트는 사전렌더링 되기어 빠른 초기 로딩 제공
데이터 페칭
데이터 페칭은 컴포넌트 클라이언트에서도 할 수 있지만, 특별한 이유가 없는 한 서버 컴포넌트에서 수행하는 것이 좋습니다.
서버 컴포넌트에서 데이터 패칭을 하면 초기 렌더링 시에 필요한 데이터를 사전에 가져와 서버에서 렌더링 된 결과를 클라이언트에 전달 할 수 있습니다. 이를 통해 초기 페이지 로드 속도를 향상 시키고 사용자 경험을 향상 시킬수 있습니다.
클라이언트 컴포넌트에서 데이터를 페칭을 하는경우는 유저와 상호작용에 의해 동적으로 데이터를 가져와야 하는 경우 입니다. 그러나 왠만해선 서버 컴포넌트에서 데이터를 페칭하는것이 좋습니다.
데이터 유형
데이터는 두가지 유형의 데이터가 있습니다.
- 정적 데이터 (Static Data) - 자주 변경되지 않는 데이터 ex) 블로그 포스트
- 동적 데이터 (Dynamic Data) - 자주 변경되는 데이터 ex) 쇼핑 카트 리스트 , 실시간으로 업데이트 되는 뉴스 피드
Static Data Fetching
fetch('https://...') // cache: 'force-cache' is the default
Revalidating Data
fetch('https://...', { next: { revalidate: 10 } })
Dynamic Data Fetching
fetch('https://...', { cache: 'no-store' })
Data Fetching Patterns
- Parallel Data Fetching ( 병렬 데이터 페칭)
- 두개의 요청이 동시에 시작되며 데이터 동시에 로드 된다.
- 서버와 클라이언트 간의 대기시간이 줄어들게 되며 데이터 로딩에 걸리는 총 시간이 감소한다.
import Albums from './albums'
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 }
}) {
// Initiate both requests in parallel
const artistData = getArtist(username)
const albumsData = getArtistAlbums(username)
// Wait for the promises to resolve
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums}></Albums>
</>
)
}
유저 경험을 향상시키기 위해 Suspense 를 사용할 수 도있다. 위와 같은 패턴은 두가지의 요청이 다 resolve 되어야지만 유저에게 데이터를 보여줄수있다. 하지만 Suspense 를 사용하여 이름을 먼저 보여주고 앨범은 로딩이 완료가 다 되면 렌더링 되게 해줄수도 있다.
import { getArtist, getArtistAlbums, type Album } from './api'
export default async function Page({
params: { username },
}: {
params: { username: string }
}) {
// Initiate both requests in parallel
const artistData = getArtist(username)
const albumData = getArtistAlbums(username)
// Wait for the artist's promise to resolve first
const artist = await artistData
return (
<>
<h1>{artist.name}</h1>
{/* Send the artist information first,
and wrap albums in a suspense boundary */}
<Suspense fallback={<div>Loading...</div>}>
<Albums promise={albumData} />
</Suspense>
</>
)
}
// Albums Component
async function Albums({ promise }: { promise: Promise<Album[]> }) {
// Wait for the albums promise to resolve
const albums = await promise
return (
<ul>
{albums.map((album) => (
<li key={album.id}>{album.name}</li>
))}
</ul>
)
}
2. Sequential Data Fetching(순차적 데이터 페칭)
// ...
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>
</>
)
}
'Next.js' 카테고리의 다른 글
Next.js Intercepting Routes 를 활용한 Modal 구현 (0) | 2023.11.12 |
---|---|
Next.js 14 App Router 가이드를 통한 Dashboard 구현 (0) | 2023.10.28 |
Nextjs - Link 태그의 Prefetch 기능 (0) | 2023.06.29 |
Next.js - Next.js 13 Metadata 동적 생성하기 (with 13.4 version) (0) | 2023.06.27 |
서버 사이드 렌더링(SSR) vs 클라이언트 사이드 렌더링(CSR) (0) | 2023.04.13 |