완전완전 그냥 제가 보고 싶어서 공식문서를 읽은 글에 불과합니다. :)
Next.js가 13 버전을 릴리즈 했다. 코드는 작성하는 순간부터 레거시 코드라는 말은 들어봤는데... 사이드 프로젝트인 알약이 Next 12버전이었으니까... 레거시 코드로 못박혀 버리니 뭔가 기분이 묘했다. 아마도 13버전에 대한 번역 및 해석들이 우수수 쏟아져 나오겠지만, 일단 내가 당장 궁금하니까
일단 보자
음 목차는
놀라웠던건 Turbopack이었다. 최대 700배가 빠른 Rust언어 기반의 웹팩 대체제라니. 미쳤나보다. (Rust가 미래..?)
app/directory에 대한 beta 도입으로 routing과 layout을 개선한다. app은 커뮤니티의 피드백을 위해 게시되었던 Layouts RFC의 결과이다...?
처음 들어보는거 튀어나왔다. Layouts RFC란다. 상당히 뒷북이지만 돌부리에 걸려 넘어졌으니 해결하고 가야겠다. 그리고 Layouts RFC와 Next.js 13이 모두 다루고 있는 부분만 보려고 한다.
app은 다음과 같은 서포트를 포함한다.
현재 내가 사용하던 12버전에서는 page폴더 아래 하위폴더로 내려가면서 라우팅이 잡혔다.
새로운 app 디렉토리에서 폴더가 경로를 정의하는데 사용된다고 한다. 경로를 만드려면 폴더안에 파일을 만들면 된다.
// app/page.js
export default function page() {
return <h1>wow</h1>
}
그리고 app 디렉토리는 page 디렉토리와 함께 작동한다고 한다. 이전 버전과의 호환성을 위해서라는데 page 디렉토리의 동작은 동알하게 유지되며 계속 지원된다고 한다. 그리고 app 디렉토리가 page 디렉토리보다 우선권을 가진다고 한다.
알약에서는 공통으로 사용할 레이아웃을 컴포넌트로 빼서 관리했다. 근데 이제 지원한다고 하넹.
layout.js 파일을 추가해서 페이지 간에 공유되는 중첩 레이아웃을 만들 수 있다고 한다.
URL 경로에 영향을 주지 않고, 형제 세그먼트 사이를 이동할 때 리렌더링이 발생하지 않는다고 하네요. 우왕.
layout에는 두가지 타입이 있다.
app폴더 하위에 layout.js파일을 만들었네. 그리고 하위 라우트인 app/blog에 blog에서 사용할 layout을 만든 모양새다.
app folder안에 layout.js파일을 추가해서 모든 라우트에 적용할 root layout을 만들 수 있다.
root layout은 모든 경로에 적용되므로 _app.js와 _document.js가 필요하다.
root layout을 사용해서 initial document shell(e.g.<html>
및<body>
)을 커스터마이즈 할수 있다.
root layout 내에서 fetch data가 가능하다고 한다.
// app/layout.js
export default function RootLayout({ children }) {
return (
<html>
<head>
<h1>Next.js Layouts RFC</h1>
</head>
<body>{children}</body>
</html>
);
}
특정 폴더 아래 layout.js 파일을 추가해서 일부 application에만 적용되는 레이아웃을 만든다. 위 그림이 잘 표현하고 있다.
blog/* 내부의 모든 경로 세그먼트에 적용이 된다.
역시 위 그림에서 잘 설명되고 있다. 루트 레이아웃은 blog 레이아웃에 적용되고, blog/* 내부의 모든 경로에도 적용된다!
아.. 기존의 page폴더 구조에서 page파일 convention이 생겼다.
각 경로에 하나씩 들어가는 UI 파일이네요. 폴더 안에 page.js를 추가해서 페이지를 만들 수 있다고 한다.
route가 유효하려면 리프 세그먼트에 page가 있어야 한다고 합니다. 그렇다면 기존의 index의 역할을 하는 아이인건가?
server-side routing에서는 데이터를 가져오고 렌더링한 후에 탐색이 이루어지므로 데이터를 가져오는 동안에 로딩 UI를 표시하는 것이 중요하다. 맞다. 표시 안해주면 유저는 이게 뭔 상황이람 할 테니까...
loading.js는 자동으로 페이지 또는 중첩된 세그먼트를 React의 Suspense Boundary로 래핑한다. Next.js는 첫 번째 로드 시 즉시 로드 구성 요소를 표시하고 형제 경로 사이를 이동할 때 다시 보여준다.
Suspense boundaries는 loading.js라는 새로운 파일 컨벤션을 사용해서 자동으로 백그라운드에서 핸들링 된다.
loading.js 파일을 폴더에 넣어서 default loading skeleton을 추가할 수 있다.
// loading.js
export default function Loading() {
return <YourSkeleton />
}
// layout.js
export default function Layout({children}) {
return (
<>
<Sidebar />
{children}
</>
)
}
// Output
<>
<Sidebar />
<Suspense fallback={<Loading />}>{children}</Suspense>
</>
Error boundaries는 자식 컴포넌트 트리에서 JavsScript error를 포착하는 React components다.
error.js는 자동으로 페이지 또는 중첩된 세그먼트를 React Error Boundary로 래핑한다. Next.js는 하위 트리에서 오류가 발생할 때마다 오류 구성요소를 표시한다. 오류가 발생할 경우 구성요소가 fallback으로 표시되고, 오류를 기록, 오류에 대한 유용한 정보를 표시, 오류 복구를 시도하는데 사용할 수 있다.
세그먼트 및 레이아웃의 중첩된 특징 때문에 Error boundaries를 만들면 UI의 해당 부분에 대한 오류를 분리할 수 있다. 오류가 발생하는 동안 경계 의위 레이아웃은 interactive 상태로 유지되고 상태가 보존된다.
error.js와 동일선상에 있는 layout.js파일 내의 오류는 Error boundaries가 레이아웃의 하위 항목을 감싸고 레이아웃 자체는 감싸지 않으므로 탐지되지 않는다.
Server Components RFC를 사용하면 Next.js 앱에 React Server Components를 점진적으로 채택할 수 있다.
One of the biggest changes between the pages and app directories is that, by default, files inside app will be rendered on the server as React Server Components.
오! pages와 app 디렉토리의 가장 큰 차이점은 app안에 파일들은 React Server Component로 렌더된다. 이제 서버 컴포넌트 알아야겠네. 다음에 정리해보자.
서버 컴포넌트는 app 폴더 또는 자신의 폴더에서 사용할 수 있지만 호환성을 위해서 페이지 디렉토리에서는 사용할 수 없다고 한다.
app 폴더는 서버, 클라이언트, 공유 컴포넌트를 지원하고 이러한 컴포넌트들을 트리에 끼울수 있다(?)
클라이언트 컴포넌트 및 서버 컴포넌트를 정의하기 위한 규칙에 관한 논의는 현재도 진행중이다.
헤더 개체, 쿠키, 경로 이름, 검색 매개 변수 등에 액세스할 수 있는 클라이언트 및 서버 컴포넌트 Hooks를 추가할 것이다!
app의 루트는 기본적으로 Static Generation을 사용하며, root segment가 요청 컨텍스트를 필요로 하는 server-side hooks를 사용할 때 dynamic rendering으로 바뀐다.
React에선 클라이언트 컴포넌트안에 서버 컴포넌트를 importing하는데 제한이 있다. 서버 컴포넌트가 server-only code를 가질 수 있기 때문이다.
다음은 동작하지 않는다.
import ServerComponent from './ServerComponent.js';
export default function ClientComponent() {
return (
<>
<ServerComponent />
</>
);
}
그러나~! 서버 컴포넌트는 클라이언트 컴포넌트의 자식으로 전달될 수 있다. 이 작업은 다른 서버 컴포넌트로 wrapping해서 할 수 있다.
// ClientComponent.js
export default function ClientComponent({ children }) {
return (
<>
<h1>Client Component</h1>
{children}
</>
);
}
// ServerComponent.js
export default function ServerComponent() {
return (
<>
<h1>Server Component</h1>
</>
);
}
// page.js
// It's possible to import Client and Server components inside Server Components
// because this component is rendered on the server
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";
export default function ServerComponentPage() {
return (
<>
<ClientComponent>
<ServerComponent />
</ClientComponent>
</>
);
}
컴포넌트가 서버에서 렌더링 되기에 가능하다고 하네요. 그러니까 이 패턴을 사용하면 React는 클라이언트에 결과를 전송하기 전에 서버에서 서버 컴포넌트를 렌더링해야 한다는 것을 알게 되고, 클라이언트 컴포넌트 관점에서 볼 때 child인 서버 컴포넌트는 이미 렌더링 된 것입니다.
layouts에서는 추가 wrapper component를 작성할 필요가 없다. children prop과 함께 적용되기 때문이라고 한다.
// The Dashboard Layout is a Client Component
// app/dashboard/layout.js
export default function ClientLayout({ children }) {
// Can use useState / useEffect here
return (
<>
<h1>Layout</h1>
{children}
</>
);
}
// The Page is a Server Component that will be passed to Dashboard Layout
// app/dashboard/settings/page.js
export default function ServerPage() {
return (
<>
<h1>Page</h1>
</>
);
}
라우트의 여러 세그먼트 내에서 데이터를 가져올 수 있다. data fetching이 page-level로 제한되어 있던 page 디렉토리와 다른 점이다.
layout.js 파일에서 Next.js의 data fetching methods인 getStaticProps
와 getServerSideProps
로 data를 fetch 할 수 있다.
// app/blog/layout.js
export async function getStaticProps() {
const categories = await getCategoriesFromCMS();
return {
props: { categories },
};
}
export default function BlogLayout({ categories, children }) {
return (
<>
<BlogSidebar categories={categories} />
{children}
</>
);
}
라우트의 여러 segment에서 data를 fetch 할 수 있다.
// app/blog/[slug]/page.js
export async function getStaticPaths() {
const posts = await getPostSlugsFromCMS();
return {
paths: posts.map((post) => ({
params: { slug: post.slug },
})),
};
}
export async function getStaticProps({ params }) {
const post = await getPostFromCMS(params.slug);
return {
props: { post },
};
}
export default function BlogPostPage({ post }) {
return <Post post={post} />;
}
Next.js는 waterfalls를 최소화하기 위해 병렬로 data fetching을 한다.
형제간 라우트 segment를 왔다갔다 할 때 Layouts은 리렌더링 되지 않고 해당 세그먼트에서 가져온다. 레이아웃을 공유하는 페이지에서 사용자가 형제 페이지 사이를 이동할 때 레이아웃은 공유되고 Next.js는 fetch만 하고 해당 segment에서 가져와서 렌더링만 한다.
이 기능은 특히 React Server Components에서 유용하다. 그렇지 않으면 각 네이게이션이 서버에서 페이지의 변경된 부분만 렌더링하는 대신에 전체 페이지가 서버에서 다시 렌더링된다. 엄청난 비효율!
아래 사진에서 유저가 /analytics
에서 /settings
로 가면, React는 page segments는 리렌더링하지만 layouts는 보존한다.
app 폴더의 계층은 URL 경로에 직접 매핑된다. 하지만 Route Groups을 생성해서 이 패턴을 벗어날 수 있다.
폴더 이름을 괄호로 감싸 만들수 있다. 아래 그림에서 layout을 공유하지 않는것을 볼 수 있다. 하하 참 신기하다.
Route Group의 이름은 URL경로에 영향을 주지 않는다.
여러개의 root layouts는 app 최상위 디렉토리에 2개 또는 그 이상의 route group을 만들면된다.
여기까지 우선... Next.js 13버전에서 소개하고 있는 부분과 Layouts RFC와 겹치는 애들은 다 본거 같다. 몇몇개가 빠졌는데 나중에 릴리즈 버전으로 올라오면 그때 봐야겠다.
여기까진 Layouts RFC내용이었다! 아래부터는 다시 Next.js 13버전 공식문서로 이동한다. 그리고 Beta Docs를 왔다갔다 거린다...
휴... 이제 다시 Next.js 13버전 공식문서를 살펴보자. 거의 중복되는 내용인거 같으니, Layouts RFC에 없던 내용이 있으면 적고 아니면 넘어가야징!
아무래도 이 내용은 꽤 중요한거 같다.
Layouts는 여러 pages에서 UI를 공유한다. 이동시에 layouts는 상태를 보존하고, 상호작용을 유지하며, 리렌더링 하지 않는다.
app 디렉토리는 React의 새로운 서버 컴포넌트 아키텍쳐를 지원한다. 아래 링크들은 Beta docs다.
next.js server and client components 를 읽어보라길래 들어갔더니 우선 fundamentals를 읽어보랜다.
next.js rendering fundamentals 읽어봐야겠지?
우선 rendering fundamentals를 살펴보자.
React 18과 Next.js 13은 우리의 앱을 렌더링하는 새로운 방식을 소개했다.
우리 앱을 렌더링하는 환경은 client
와 server
두가지가 있다.
React 18 이전에는 앱을 렌더링 하는 기본적인 방식은 전적으로 클라이언트에 있었다.
Next.js는 앱을 더 쉽게 pages(Next.js pages 디렉토리를 의미하는거 같다.)로 break down 할 수 있는 방식을 제공했고, 서버에서 HTML을 생성함으로써 prerender하며, React에 의해서 hydrated되도록 client로 보냈다.
하지만 이 방식은 초기 HTML을 interactive하기 위해 clent에 추가적인 JS를 추가할 필요가 있었다.
이제는 component level에서 렌더링 환경을 선택할 수 있다. 죽, React는 client와 server 위에서 모두 렌더링 가능하다. 기본적으로, app 디렉토리
는 Server Component를 사용하고, 이는 서버 위에서 컴포넌트를 렌더링하기 쉽게 하며, client로 보내는 JS의 양을 줄였다. 그렇지만, app안에서도 Client Components를 선택해서 client위에서 렌더링 할 수 있다. 어디까지나 선택이라는 것!
Static Rendering은 Server와 Client 컴포넌트 둘 다 서버에서 build time에 prerender 된다. 결과물은 cached되고 후속 요청에서 재사용된다.
SSG(Static Site Generation)와 ISR(Incremental Static Regeneration)과 동일하다.
Server와 Client 컴포넌트들은 Static Rendering동안 다르게 렌더링 된다.
Dynamic Rendering은 Server와 Client 컴포넌트 둘 다 server에서 request time에 렌더링된다. 해당 작업은 Static Rendering과 달리 cached되지 않는다.
Server Side Rendering(getServerSideProps())과 동일하다.
서버 위에서, page를 렌더링하는 런타임은 두가지다.
두 방식 모두 배포 인프라에 따라 서버에서 데이터를 스트리밍 할 수 있다.
fundamental 끗!
드디어 다시 Server and Client Components다. 🤸♂️
Server 컴포넌트를 통해 개발자는 서버 인프라를 보다 효과적으로 사용할 수 있다. 예를 들면, 이전에 client에서 JS 번들 사이즈 크기에 영향을 미쳤던 커다란 의존성은 온전히 서버에 남기에, 퍼포먼스 향상을 이끌어낼 수 있다. 그렇지만 app 디렉토리는 여전히 JS를 필요로 한다.
When a route is loaded, the Next.js and React runtime will be loaded,
which is cacheable and predictable in size.
This runtime does not increase in size as your application grows.
Further, the runtime is asynchronously loaded, enabling your HTML from the server to be progressively enhanced on the client.
이 부분은 잘 이해가 가지 않는다... 경로가 로드되면, 캐시 가능하고 크기를 예측 가능한 Next.js와 React runtime이 로드된다. 이 runtime은 당신의 앱이 커져도 증가하지 않는다. 또한 runtime은 비동기적으로 로드되고, 서버에서 온 HTML을 client에 점진적으로 향상시킬 수 있다(?). 점진적으로 받아올 수 있다는 얘긴가?
추가적인 JS는 client-side의 상호작용이 필요할 때만 추가된다.
Client 컴포넌트는 client위에서 렌더링 된다. Next.js에서는 Client Components는 서버에서 pre-rendered가 가능하고 client에서 hydrated된다.
Client Component를 사용하기 위해서 app 안에 file을 만들고 "use client"를 code에 추가한다.
use strict가 생각나는 대목이다...
// app/Counter.js
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
Client Component가 필요해 지기까지는 Server Component를 사용!
Client 컴포넌트에서 데이터를 가져올 수 있지만, 특별한 이유가 없다면 서버 컴포넌트에서 데이터를 가져오는것을 추천한다. 서버로 data fetching을 옮기는 것이 성능과 사용자 경험을 향상시킨다.
Server 컴포넌트에서 Client 컴포넌트로 보낸 Props는 직렬화가 필요하다. 함수, 날짜 등과 같은 값을 client컴포넌트로 직접 전달할 수 없다는 의미이다.
의도하지 않은 클라이언트 코드 사용을 위해서 서버 전용 패키지를 깔아서 미연에 방지(build time error)할 수 있다고 합니다.
휴! 다시 Next.js 13 docs로 가자.
그리고 도저히 하나의 포스트로 끝낼 자신이 없어졌다. 13버전이 궁금했고, 시작부터 Layouts RFC가 튀어나오길래 또 궁금해서 본게 이렇게 양이 많아질 줄이야...
여기까지 읽었다.
다음에 Streaming부터 읽어야겠다.
Next.js 13
Nextjs Layouts RFC
Next.js Layouts RFC in 5minutes (vercel)
Next.js Beta Rendering: Server and Client Components
Next.js Beta Rendering: fundamentals
잘 봤습니다 ^^