Next.js 13 버전에 App 디렉토리가 신규로 추가되었습니다.
🚧 아직 실험적인 기능이며, 안정적이지 않기 때문에 프로덕션 환경에서 되도록 사용하지 않는 것을 권장합니다.
App 디렉토리 소개
App 디렉토리 사용시 폴더 구조와 Data Fetch 방법들이 완전히 다르게 변경되는데요.
기존에는 index 파일안에 레이아웃을 짜고 알아서 파일을 분리해서 레이아웃 구성해야 했다면, 이제는 각 레이아웃을 파일로 분리시킵니다.
즉, App 디렉토리의 폴더 구조 자체가 App의 명세서가 됩니다.
또한 App 디렉토리 아래에 작성된 모든 컴포넌트는 Server Component 가 됩니다.
기존에 저희가 사용해왔던 컴포넌트들은 모두 Client Component 입니다.
즉, Client 측에서 상태를 변경하고 리렌더링 해야하는 경우 (useState, useEffect 사용시) 에 해당하는데요
App 디렉토리에서 Server Component 가 아니라 Client Component 를 사용하고 싶다면,
파일 최상단에 "use client" 지시문을 추가 해주기만 하면 원래 사용하던 방식인 Client Component 로 사용할 수 있습니다.
(사실 이 경우 굳이 App 디렉토리에 두지 않고 다른 폴더로 옮겨도 됩니다.)
Server Component 를 사용하면
client->server로 데이터 요청하는게 아니라 server->server로 데이터를 요청하므로 클라이언트 측에서 불필요한 렌더링을 줄여주고, API 호출이 줄어들기 때문에 당연히 성능에서 큰 이득을 볼 수 밖에 없습니다.
Client / Server Component 의 차이는 아래와 같이 정리됩니다.
참고로
서버 사이드 렌더링은 초기 HTML 를 서버에서 렌더링해서 전달하여 사용자에게 페이지를 빠르게 보여주는것이 목적이고,
서버 컴포넌트는 서버에서 데이터 Fetch 및 다운 받아야하는 종속성 등이 다 처리된 후 클라이언트에게 최소한의 자바스크립트 번들을 제공하여 성능을 극대화 하는 것이 목적입니다.
또한 아래는 반드시 기억해야 하는 것들입니다.
서버 컴포넌트는 서버/클라이언트 컴포넌트를 모두 import 할 수 있지만, 클라이언트 컴포넌트는 클라이언트 컴포넌트만 import 할 수 있습니다. 그 대신에 클라이언트 컴포넌트에 서버 컴포넌트를 children 으로 넘기거나 props로 전달하는건 가능한 것으로 보이네요.
서버 컴포넌트에서 클라이언트 컴포넌트로 전달되는 props는 직렬화 가능해야 합니다. 함수, 날짜 같은 것들은 전달할 수 없습니다.
App 디렉토리 적용
next.config.js 구성 파일에서 appDir 옵션을 활성화 해줍니다.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
+ experimental: {
+ appDir: true
+ }
...
}
module.exports = nextConfig
그리고 /pages 폴더 삭제하고 /app 폴더 생성을 해줍니다.
tsconfig.json 에 필요한 설정은 알아서 해준다고합니다. 😉
/app 구조
- Head
- <head> 에 들어가는 메타 데이터 입니다.
- @next/head 와 동일합니다.
- Layout
- RootLayout
- /app 경로 최상위 Layout 입니다. 컴포넌트 이름을 RootLayout 로 지어야 합니다.
- 모든 하위 경로에 공통적으로 들어가는 코드 입니다.
- _app.tsx, _document.tsx 와 동일합니다.
- NestedLayout
- 특정 페이지 안에서만 공통적으로 들어가는 코드 입니다. 컴포넌트 이름을 Layout 로 지어야 합니다.
- RootLayout
- Page
- 각 페이지의 루트 파일입니다.
- /pages/index 파일과 동일합니다.
Dynamic Routes
app
|__ ...
|__ posts
| |__ loadng.{js.tsx}
| |__ error.{js.tsx}
| |__ [slug].{js,tsx}
- loading
- 데이터 Fetch 시 Fallback Loading 화면으로 사용됩니다.
- React Suspense 를 써서 수동으로도 설정할 수 있습니다.
- error
- 데이터 Fetch 실패시 화면으로 사용됩니다
- 반드시 Clilent Component 로 만들어야 합니다.
- props로 error, reset() 을 기본적으로 전달받습니다. reset() 함수를 통해 오류 복구를 시도 할 수 있습니다.
- 그러나 루트 레이아웃의 오류를 처리해주지는 않습니다. 그래서 /app/global-error.js 를 만들어서 전역 에러 바운더리를 설정해주어야 합니다.
- slug
const getPost = async (id: string) => {
const res = await fetch(`https://..../${id}`, { next: { revalidate: 10 } } )
const data = await res.json()
return data
}
export default async function Page({ params }: any) {
const data = await getPost(params.id)
return (
<div>
{data.id}
{data.name}
</div>
)
}
- generateStaticParams
- getStaticPaths 와 동일합니다.
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({
slug: post.slug
}))
}
예시
Layout 는 클라이언트 State가 필요 없고 한번 렌더링된 후에 리렌더링 될 일이 없기 때문에 서버 컴포넌트로 만들었습니다.
Logo 도 마찬가지의 이유로 서버 컴포넌트로 만들었습니다.
SearchBar 는 유저와 상호작용 하며 클라이언트 State 를 사용해야하고 리렌더링이 되어야 합니다.
따라서 클라이언트 컴포넌트로 만들었습니다.
// SearchBar is a Client Component
import SearchBar from './SearchBar';
// Logo is a Server Component
import Logo from './Logo';
// Layout is a Server Component by default
export default function Layout({ children }) {
return (
<>
<nav>
<Logo />
<SearchBar />
</nav>
<main>{children}</main>
</>
);
}
타사 라이브러리 호환성 문제
아래와 같이 한번 더 래핑해서 "use client" 지시문으로 "클라이언트 기능을 사용하고 있습니다." 라고 알려줘야 합니다.
'use client';
import { AcmeCarousel } from 'acme-carousel';
export default AcmeCarousel;
데이터 가져오기
특별히 클라이언트 컴포넌트에서 가져올 필요가 없다면 서버 컴포넌트에서 가져와야 합니다.
클라이언트 컴포넌트에서 가져와야 하는 경우가 있을까요? 아마도 무한 스크롤 같은 경우가 있을 수 있겠네요...
useState 와 useEffect 를 사용하여 데이터를 계속 누적시켜야 되기 때문에 클라이언트 측에서 해야될 것 같네요.
서버 컴포넌트에서는 확장된 fetch 를 사용하면 되고,
클라이언트 컴포넌트에서는 SWR, React Query 와 같은 타사 라이브러리 사용이 권장됩니다.
만약 기본 fetch API 사용 하지 않으면 아래와 같이 다양한 변수를 export 하여 캐싱 설정할 수 있다고 합니다.
export const dynamic = 'auto',
dynamicParams = true,
revalidate = 0,
fetchCache = 'auto',
rumtime = 'nodejs',
preferredRegin = 'auto
생성되지 않은 dynamic 페이지 접근 시 행동 설정
- true : generateStaticParams 에 포함되지 않은 페이지일 경우 접근시 페이지 생성
- false : generateStaticParams 에 포함되지 않은 페이지일 경우 접근시 404
export const dynamicParams = true // true | false,
Next.js 는
- auto: (기본값) 동적 함수를 실행하기 전 접근할 수 있는 모든 fetch 요청을 캐싱하고 동적 함수를 실행한 후 접근하는 fetch 요청은 캐싱하지 않습니다.
- default-cache: fetch 동적 함수 이후의 요청도 정적으로 간주됩니다.
- only-cache: 기본값을 force-cache 로 변경합니다. 옵션 제공없이 에러 발생하면 no-store 로 동작합니다.
- force-cache: 모든 fetch 요청을 캐싱
- default-no-store: fetch 동적 함수 이전의 요청도 동적으로 간주됩니다.
- only-no-store: 기본값으로 캐싱하지 않습니다. 옵션 제공없이 에러 발생하면 force-cache 로 동작합니다.
- force-no-store: 캐싱하지 않습니다. 모든 요청을 강제로 다시 가져옵니다.
export const dynamic = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
참고
- https://beta.nextjs.org/docs/rendering/server-and-client-components
'프론트엔드 개발 > Next.js' 카테고리의 다른 글
[프로젝트 기록] Next.js 의 SSG 는 너무 느려서 못써먹겠다. (4) | 2024.09.30 |
---|---|
Next.js + React Native 크로스 플랫폼 개발을 위한 Solito, NativeWind 소개 (0) | 2023.10.03 |
Next.js 에서 소셜 로그인 구현하기 (next-auth.js 사용) (1) | 2023.02.18 |
Next.js 13 버전에서 ReactQuery 사용시 서버 컴포넌트에서 클라이언트 컴포넌트로 pre-fetch 데이터 전달하는 방법 (4) | 2023.02.05 |
웹 프레임워크 Next.js 는 무엇인가요? (프론트 면접 질문) (0) | 2023.02.05 |