nextjs하면 seo가되서 검색이 잘되고 이미지리사이즈 자동으로 해주고 코드스플리팅해주고 폴더구조라우팅도되고 솰라솰라 좋은점이 아주많은 리액트를 도와주는 프레임워크라고 생각했다
근데 vercel에서 뭐 엄청난 대격변을 했는지 Page라는 디렉터리에서 App으로 바꿨더라.. 이게 바꾸는게 무슨의미인지 궁금해졌다
근데 알아볼 방법을 모르겠어서 그냥저냥 살던중 메일함에 Korean FE Article에 App디렉터리특집으로 뉴스레터가 왔다!!
(번역) Next.js의 app 디렉터리 아키텍처 이해하기
그래서 간략하게 한시간동안 이해할수있을만큼만 정리하면서 읽어보려고 한다
App디렉터리는 리액트에서 새로나왔던 RSC(리액트 서버 컴포넌트)를 실제로 쓸수있게 구현한건데 라우트를 처리하고 뷰를 렌더링하기위한 새로운전략이라고 한다
이 전략은 몇가지 기능을 하나로 묶은건데 리액트의 동시성 기능을
최대한 활용할 수 있도록 고안되었다고 한다(Suspense)
이 새로운 앱 빌드방식은 매우 환영할만한 많은 개선점을 제공하는데
1. 부분라우팅
이렇게 세가지다
비교해보면
Page디렉터리의 경우 개발자는 라우트별로 데이터를 가져와야 했다
getServerSideProps: 서버사이드렌더링
getStaticProps: 서버사이드 프리렌더링 그리고/또는 증분 정적재생성
getStaticPaths + getStaticProps: 서버 사이드 프리 렌더링 또는 정적 사이트 생성
지금까지는 페이지단위로 렌더링전략을 선택할 수 없었고
대부분의 앱은 전체적으로 ssr이나 ssg를 사용했다
Next.js는 아키텍처 내에 개별적으로 라우트를 생각하는 것이 표준이 될 만큼 충분한 추상화를 만들었다.
import { type AppProps } from "next/app";
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<SomeProvider>
<Component {...pageProps} />
</SomeProvider>
);
}
라우터별로 필요한 데이터를 구성, 렌더링 하는 기능 덕분에 이 접근 방식은 앱에서 데이터를 전역적으로 사용해야 할 때 매우 유용한 도구가 되었습니다. 이 전략을 사용하면 데이터를 앱 전체에 뿌릴 수 있지만, 모든 것을 컨텍스트 Provider로 래핑하면 하이드레이션이 앱 루트에 묶이게 됩니다. 즉, 더 이상 서버에서 해당 트리(해당 컨텍스트의 Provider내에 있는 모든 라우트)의 브랜치를 렌더링 할 수 없습니다.
=>이것도 사실 이해 잘 안됨
여기서, 레이아웃 패턴이 도입됩니다. 페이지 주위에 래퍼를 생성하면 앱 전체의 렌더링 전략을 결정하는 대신 라우트별로 렌더링 전략을 다시 선택하거나 해제할 수 있게됩니다. 페이지 디렉터리에서 상태를 관리하는 방법에 대한 자세한 내용은 “Next.js의 상태관리” 문서와 Next.js 공식 문서를 참고하세요.
레이아웃 패턴은 훌륭한 솔루션이었습니다. 렌더링 전략을 세밀하게 정의할 수 있다는 것은 매우 환영할 만한 부분이었습니다. 그래서 App 디렉터리에 레이아웃 패턴을 전면에 배치했습니다. Next.js 아키텍처의 일급 객체(first-class citizen)로서 성능, 보안 및 데이터 처리 측면에서 엄청난 개선을 이룰 수 있었습니다.
=>레이아웃패턴이 도입되어서 페이지별로 렌더링전략을 다시선택하거나 해제할 수 있다는 말인것같다
리액트 Concurrent 기능을 사용하면 이제 컴포넌트를 브라우저로 스트리밍하고, 각 컴포넌트가 자체 데이터를 처리하도록 할 수 있습니다. 따라서 이제 렌더링 전략이 페이지 전체가 아닌 컴포넌트 기반으로 훨씬 더 세분화됩니다. 레이아웃은 기본적으로 중첩되므로 개발자는 파일 시스템 아키텍처에 따라 각 페이지에 어떤 영향을 미치는지 더 정확하게 파악할 수 있게 됩니다. 무엇보다 컨텍스트를 사용하려면 “use client” 지시문을 통해 컴포넌트를 클라이언트 사이드로 명시적으로 전환해야 합니다.
=> App디렉터리에서는 Concurrent기능 덕분에 페이지가아니라 컴포넌트별로 렌더링전략을 다르게 할 수 있다는 말인것같다
이 아키텍처는 페이지 별 레이아웃 아키텍처를 기반으로 합니다. 이제 _app 컴포넌트도 없고 _document 컴포넌트도 없습니다. 이 두 컴포넌트는 모두 루트 layout.jsx 컴포넌트로 대체되었습니다. 예상하셨겠지만, 이는 전체 애플리케이션을 래핑하는 특별한 레이아웃입니다.
export function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
루트 레이아웃은 서버가 전체 앱에 반환하는 HTML을 한 번에 조작하는 방법입니다. 루트 레이아웃은 서버 컴포넌트이며 페이지 이동시 다시 렌더링되지 않습니다. 즉, 레이아웃의 모든 데이터나 상태는 앱의 수명주기 내내 유지됩니다.
루트 레이아웃은 전체 앱을 위한 특별한 컴포넌트지만, 다른 구성요소를 위한 루트 컴포넌트도 가질 수 있습니다.
loading.jsx: 전체 라우트의 Suspense 바운더리를 정의할 수 있습니다.
error.jsx: 전체 라우트의 에러 바운더리를 정의할 수 있습니다.
template.jsx: 레이아웃과 유사하지만, 모든 페이지 이동시 다시 렌더링합니다. 인/아웃 트랜지션과 같은 경로 간 상태를 처리하는데 특히 유용합니다.
이러한 컴포넌트와 규칙은 기본적으로 중첩됩니다. 즉, /about은 자동으로 /의 래퍼 안에 중첩됩니다.
마지막으로, 모든 라우트에 대한 page.jsx가 있어야 하는데, 이는 해당 URL 세그먼트(당신이 컴포넌트를 넣는 위치로 알고 있는)에 대해 렌더링할 주요 컴포넌트를 정의하기 때문입니다. 이 컴포넌트는 기본적으로 중첩되지 않으며, 해당 URL 세그먼트와 정확하게 일치하는 경우에만 DOM에 표시됩니다.
아키텍처에는 훨씬 더 많은 기능이 추가될 예정이지만, 이정도면 프로덕션 환경에서 Page 디렉터리를 App 디렉터리로 마이그레이션을 고려하기 전 멘탈 모델을 세우기에 충분할 것입니다. 꼭 공식 업그레이드 가이드도 확인하세요.
=>앱디렉토리는
RootLayout: _app + _document컴포넌트
loading: Suspense바운더리 정의 =
error:에러바운더리
template:레이아웃과 유사하지만 리렌더링됨
로 되어있고 일반폴더에는 layout, page도 있다
리액트 서버 컴포넌트를 사용하면 앱은 인프라를 활용해 성능과 전반적인 사용자 경험을 개선할 수 있습니다. 예를 들어 RSC는 최종 번들에 종속되지 않기 때문에 번들 크기가 즉각적으로 개선됩니다. 그리고 서버에 렌더링 되기 때문에 모든 종류의 파싱, 포맷팅 또는 컴포넌트 라이브러리가 서버에 남게 됩니다. 또한, 비동기적인 특성 덕분에 서버 컴포넌트는 클라이언트로 스트리밍됩니다. 이를 통해 렌더링 된 HTML은 브라우저에서 점진적으로 개선될 수 있습니다.
따라서 서버 컴포넌트는 앱 크기와 번들 크기 사이의 선형적 상관관계를 깨고 최종 번들의 크기를 보다 예측할 수 있고 캐싱할 수 있으며 일정하게 유지할 수 있게 합니다. 따라서 RSC는 기존 리액트 컴포넌트(현재는 혼동을 피하기 위해 클라이언트 컴포넌트로 부름)에 비해 모범 사례로 즉시 자리 잡았습니다.
서버 컴포넌트에서 데이터 페칭은 훨씬 유연하며, 제 생각에는 바닐라 자바스크립트에 더 가깝게 느껴져 러닝 커브가 낮다고 느껴집니다. 예를 들어 자바스크립트 런타임을 이해하면 데이터 페칭을 병렬 또는 순차적으로 정의할 수 있으므로 리소스 로딩 워터폴을 더욱 세밀하게 제어할 수 있게 됩니다.
import TodoList from "./todo-list";
async function getUser(userId) {
const res = await fetch(`https://<some-api>/user/${userId}`);
return res.json();
}
async function getTodos(userId) {
const res = await fetch(`https://<some-api>/todos/${userId}/list`);
return res.json();
}
export default async function Page({ params: { userId } }) {
// 두 요청을 동시에 시작합니다.
const userResponse = getUser(userId);
const todosResponse = getTodos(username);
// 모든 프라미스가 이행될 때까지 기다립니다.
const [user, todos] = await Promise.all([userResponse, todosResponse]);
return (
<>
<h1>{user.name}</h1>
<TodoList list={todos}></TodoList>
</>
);
}
async function getUser(username) {
const res = await fetch(`https://<some-api>/user/${userId}`);
return res.json();
}
async function getTodos(username) {
const res = await fetch(`https://<some-api>/todos/${userId}/list`);
return res.json();
}
export default async function Page({ params: { userId } }) {
const user = await getUser(userId);
return (
<>
<h1>{user.name}</h1>
<Suspense fallback={<div>Fetching todos...</div>}>
<TodoList userId={userId} />
</Suspense>
</>
);
}
async function TodoList({ userId }) {
const todos = await getTodos(userId);
return (
<ul>
{todos.map(({ id, name }) => (
<li key={id}>{name}</li>
))}
</ul>
);
}
Page는 getUser 페칭을 기다린 다음 렌더링을 시작합니다. \<TodoList>에 도달하면, getTodos를 페칭한 뒤 기다립니다. 이것은 페이지 디렉터리에서 사용하던 것보다 더 세분되어 있습니다.
=>두 요청을 동시에 던지고 따로받거나 요청과 렌더링에 순서를 줄 수 있다
구문은 fetch(route, options)로 동일합니다. 그러나 웹 Fetch 사양에 따르면 options.cache는 이 API가 브라우저 캐시와 상호작용하는 방식을 결정합니다. 하지만 Next.js에서는 프레임워크 서버 사이드 HTTP 캐시와 상호작용 합니다.
Next.js의 확장된 Fetch API 및 캐시 정책과 관련해 두 가지 값을 이해하는 것이 중요합니다.
force-cache: 기본값. 새로운 match를 찾아 반환합니다.
no-store 또는 no-cache: 모든 요청에 대해 원격 서버에서 페칭합니다.
next.revalidate: ISR과 동일한 구문이며, 리소스를 새 리소스로 간주하는 엄격한 임계값을 지정합니다.
fetch(`https://route`, { cache: "force-cache", next: { revalidate: 60 } });
=>확장된 fetch api를통해 cache정책을 다룰 수 있게 하려나보다
이 글에서 언급한 모든 이점을 제공하기 위해 RSC는 상호작용할 수 없으며, 이는 훅이 없다는 것을 의미합니다. 따라서 클라이언트 사이드 로직을 렌더링 트리의 하위 컴포넌트로 최대한 늦게 푸시하기로 했으며, 상호작용 기능을 추가하면 해당 컴포넌트의 자식이 클라이언트 사이드가 됩니다.
일부 컴포넌트를 푸시하는 것이 불가능한 경우도 있습니다. 예를 들어 일부 핵심 기능이 리액트 컨텍스트에 의존하는 경우가 있습니다. 대부분의 라이브러리는 프로퍼티 드릴링으로부터 사용자를 방어할 준비가 되어있기 때문에, 많은 라이브러리가 루트에서 먼 자식 컴포넌트로 건너뛰는 컨텍스트 Provider를 생성합니다. 따라서 리액트 컨텍스트를 완전히 버리면 일부 외부 라이브러리가 제대로 작동하지 않을 수 있습니다.
=>상태도 못바꾸고 상호작용할수도없고 훅도없고 RSC 뭔데ㅋㅋ
지금까지 Next.js에는 데이터 변이 시나리오가 내장되어 있지 않았습니다. 클라이언트 사이드에서 실행되는 요청은 개발자의 재량에 따라 알아내야 했습니다. 하지만 리액트 서버 컴포넌트는 다릅니다. 리액트 팀은 프라미스를 수락한 다음 처리하고 값을 직접 반환하는 use훅을 개발중입니다.
앞으로는 use훅이 현업에서 사용되는 useEffect의 나쁜 사례를 대체할 것이며(이에 관한 자세한 내용은 "Goodbye UseEffect"에서 확인할 수 있습니다.), 클라이언트 사이드 리액트에서 (페칭을 포함한)비동기성을 처리하는 표준이 될 수 있습니다.
당분간은 클라이언트 사이드 페칭 요구사항에 대해 React-Query및 SWR과 같은 라이브러리를 사용하는 것이 좋습니다. 하지만, fetch동작에 특히 유의하세요!
=>리액트팀은 RSC관련 데이터뮤테이션 관련 use훅을만들고있다고 한다 useEffect의 나쁜사례를대체한다고한다
음... 무슨말인지 솔직히 잘 모르겠다
이걸읽고 떠오르는대로 적어보면
기존 리액트에서는 앱전체에서 렌더링전략을 썼고
Page디렉토리에서는 페이지마다 렌더링전략을 바꿀 수 있었고
App디렉토리에서는 RSC Concurrent이런거때문에
컴포넌트마다 렌더링전략을 바꿀 수 있다
RSC는
1.최종 번들에 종속되지 않기 때문에 번들 크기가 즉각적으로 개선됩니다.
=>번들크기가 작으면 초기 로딩속도도 빠르겠지
2.서버에 렌더링 되기 때문에 모든 종류의 파싱, 포맷팅 또는 컴포넌트 라이브러리가 서버에 남게 됩니다.
=>데이터를 처리하는부분은 서버에있고 프레젠테이션에 보여줄것만 보내면되니까 더작다
3.비동기적인 특성 덕분에 서버 컴포넌트는 클라이언트로 스트리밍됩니다.
=>1)서버컴포넌트 요약에서처럼 페칭전략을 자기가 정할 수 있으니까 컴포넌트단위로 데이터가 스트리밍되는것처럼 할수있나보다
따라서 서버 컴포넌트는 앱 크기와 번들 크기 사이의 선형적 상관관계를 깨고 최종 번들의 크기를 보다 예측할 수 있고 캐싱할 수 있으며 일정하게 유지할 수 있게 합니다. 따라서 RSC는 기존 리액트 컴포넌트(현재는 혼동을 피하기 위해 클라이언트 컴포넌트로 부름)에 비해 모범 사례로 즉시 자리 잡았습니다.
서버 컴포넌트에서 데이터 페칭은 훨씬 유연하며, 제 생각에는 바닐라 자바스크립트에 더 가깝게 느껴져 러닝 커브가 낮다고 느껴집니다. 예를 들어 자바스크립트 런타임을 이해하면 데이터 페칭을 병렬 또는 순차적으로 정의할 수 있으므로 리소스 로딩 워터폴을 더욱 세밀하게 제어할 수 있게 됩니다.
=> 위 이유로 모범사례가 되었다고함
그래서 이런 리액트 18의 기능을 쓸수있어서
App디렉터리가 좋은건가보다~