작년 하반기에 Next.js 13이 발표가 되었다.
소개되는 주요 변경된 내용은 다음과 같다.
내용을 요약하면 '좋아졌다!' 입니다.
더 작아지고, 더 빨라지고, 더 효율적이고, 코딩도 단순하게 바뀐 부분도 있고 등등등.
이번 글 에서는 이 새로운 기능을 사용하여 프로젝트를 시작하기 위해 필요한 최소한의 내용만을 다루도록 하겠습니다.
이번 블로그의 내용은, YouTube의 'Next 13 Basics — Components outside or inside App folder?' 동영상의 내용을 기준으로 약간의 설명을 추가 하겠습니다.
npx create-next-app@latest --experimental-app
'--experimental-app' 옵션을 추가하면, app폴더를 사용하는 새로운 구조로 프로젝트가 생성 됩니다.
기존에 생성된 프로젝트에 대한 자세한 업그레이드 방법은 공식문서 를 참고하시면 됩니다.
라우팅 방식이 변경되었습니다.
v12 이전버전(이후로는 v12라고 명칭 하겠습니다) 에는 file 단위로 라우팅이 처리 되었지만 v13 'app Directory' (이후로는 그냥 v13)적용 후에는 dirctory 기준 라우팅 방식으로 변경 되었습니다.
화면에 Layout을 구성하는 코드가 들어갑니다.
부모 폴더에서 정의된 layout.tsx는 자식 폴더까지 적용 됩니다.
변경이 필요 한 자식 폴더에서는 layout.tsx를 작성하여 구성을 overriding 할 수 있습니다.
외부 콤포넌트 사용 등의 이유로 v12 까지에서 사용하던 '_app.tsx' 또는 '_document.tsx'에 추가했던 내용은 root 폴더의 layout.tsx에 구성하면 됩니다.
라우팅 path를 폴더로 지정하면 v13에서는, 해당 폴더의 index.tsx가 아닌 page.tsx 파일을 읽고 (layout.tsx 등 예약된 파일이 있다면 그것과 함께 묶어서) 페이지를 출력합니다.
그로 인해 v12에서는 오류가 나던, component나 유틸리티 등의 소스 코드를 app 폴더 내에 작성해도 됩니다.
app 폴더에 laoyout.tsx, page.tsx 두개의 파일만 남기고 모두 삭제 합니다.
간단한 테스트를 위해서 생성된 코드들을 간단히 변경 합니다.
//
// app/layout.tsx
//
// import './globals.css' -- 삭제
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>{children}</body>
</html>
);
}
global.css의 삭제는 테스트를 위한 것으로,
실 프로젝트에서는 전체에 적용 될 스타일 파일을 작성하여 import 하는것이 좋습니다.
//
// app/page.tsx
//
export default function Home() {
return <p>이것은 메인 페이지 입니다.</p>;
}
'yarn dev'로 앱을 실행하고 브라우저에서 'http://localhost:3000'으로 접속하면 아래와 같은 화면을 볼 수 있습니다.
app 폴더 아래에 'event' 폴더를 만듭니다.
event 폴더에 page.tsx 파일을 아래와 같이 생성 합니다.
//
// app/event/page.tsx
//
export default function Event() {
return <p> 이것은 이벤트 페이지 입니다. </p>;
}
그리고 브라우저에서 'http://localhost:3000/event' 로 접속하면 아래와 같은 화면을 볼 수 있습니다.
path parameter 테스트를 위해
'event' 폴더 하위에 '[id]' 폴더를 생성하고 page.tsx 파일을 만듭니다.
//
// app/event/[id]/page.tsx
//
export default function EventId() {
return <p> 이것은 이벤트 아이디 페이지 입니다. </p>;
}
그리고 브라우저에서 'http://localhost:3000/event/dfadfa' 로 접속하면 아래와 같은 화면이 출력 됩니다.
app 폴더 하위에 components 폴더를 만들고, header.tsx 파일을 생성 합니다.
//
// app/components/header.tsx
//
export default function Header() {
return <p>헤더 콤포넌트 입니다.</p>;
}
그리고 app/layout.tsx를 수정 합니다.
//
// app/layout.tsx
//
import Header from './components/header'; // 추가
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<Header /> {/* 추가 */}
{children}
</body>
</html>
);
}
이후에는 모든 페이지에서 샘플로 작성한 header component가 적용된 모습을 볼 수 있습니다.
최종 파일 구성은 아래와 같습니다.
v12에서 사용되어온 getServersideProps, getStaticPaths, setStaticProps를 대체하는 데이터 패칭 방법이 소개 되었습니다.
복잡도가 많이 줄고, 코딩양도 줄고, 여러모로 좋아졌습니다.
이 변화는 개인적으로 제일 마음에 드는 부분 입니다.
간단히 테스트를 위한 api를 작성합니다.
생성된 pages/api/hello.ts 를 수정하여 사용하도록 하겠습니다.
//
// pages/api/hello.ts
//
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
type Post = {
title: string;
content: string;
};
type Data = {
posts: Post[];
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
const data = await new Promise((resolve) => {
setTimeout(resolve, 3000);
});
res.status(200).json({
posts: [
{
title: 'This is my new post',
content: 'This is the content',
},
{
title: 'This is another new post',
content: 'This is the content of this another post',
},
],
});
}
데이터 구조를 번경하였고, 함수에 async 적용과, response 시간의 delay를 주었습니다.
작동 테스트는 아래와 같습니다.
$ curl localhost:3000/api/hello
위의 명령을 실행하면 5초 후에 결과가 출력되는것을 확인할 수 있습니다.
다음은 데이터를 조회하는 샘플을 작성 합니다.
//
// app/page.tsx
//
async function getPosts() {
const data = await fetch('http://localhost:3000/api/hello');
const json = await data.json();
return json?.posts as any[];
}
export default async function Home() {
const posts = await getPosts();
return (
<div>
{posts?.map((post, index) => (
<div key={index}>
<p>{post.title}</p>
<p>{post.content}</p>
</div>
))}
</div>
);
}
v12에서의 복잡한 serverside 함수가 없어져 매우 간결하고 가독성이 좋아졌습니다.
next에서 fetch함수를 재구성하여, loading 처리도 간결하게 할 수 있게 되었습니다.
app 디랙터리에 loading 콤포넌트를 만듭니다.
(참고 -loading 콤포넌트는 파라메터를 차리 할수 없습니다)
//
// app/loading.tsx
//
export default function Loading() {
return <p>Loading ...</p>;
}
layout.tsx에 Loading 콤포넌트를 적용 합니다.
import { Suspense } from 'react';
import Header from './components/header';
import Loading from './loading'; // 추가
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>
<Header />
{/* 수정 */}
<Suspense fallback={<Loading />}>{children}</Suspense>
</body>
</html>
);
}
데이터를 조회하는 동안 '{children}' 부분에 작성한 loading 콤포넌트가 출력되는것을 확인할 수 있습니다.
v13 에서는 async가 정의된 page에 waitting이 걸리면, 자동으로 suspense가 처리되도록 구성되어 있습니다.
v13의 fetch 함수에는 캐쉬 사용 옵션들이 제공 됩니다.
async function getPosts() {
const data = await fetch('http://localhost:3000/api/hello', {
// cache: 'force-cache',
next: {
revalidate: 10,
},
});
const json = await data.json();
return json?.posts as any[];
}
fetch에는 여러 cache 옵션이 제공됩니다. (cache 옵션의 default는 'force-cache' 입니다.)
하지만 v13에서는 revalidate 설정으로 처리하면 됩니다.
'app Directory' 기능을 이용하여 개발할 때 필요한 최소한의 변경 사항에 대해서만 설명 하였습니다.
아직 beta 기능이기 때문에 production에 사용할 지 의 여부는 판단해야 합니다. (개발 현황)
v12는 SSR (Server Side Rendering)을 지원하는 방향 이였지만 v13에서는 SSR을 기본으로 CSR (Client Side Rendering)을 포함하는 형태인것 같습니다.
이로 인해 기존과 달라진 부분이 server-side-component와 client-side-component의 구별과 사용에 대한 문제가 있습니다.
user interection이 있는 부분들, 로컬에서의 데이터 처리를 위한 hook 코드가 있는 경우는 client 콤포넌트로 지정(코드 최상단에 'use client' 추가)하면 되는데, 아직 외부(UI 콤포넌트 등)콤포넌트들은 client compoent 정의가 되어 있지 않아 하나하나의 콤포넌트를 재정의 하거나, 전체 페이지를 'use client'로 정의 해야 하는 아쉬운 부분도 존재 합니다.
data fetch 부분도 완전히 개발이 완료된 상태는 아닌것 같습니다.
하지만 모든 페이지를 'use client'로 지정하고 client component 로 정의해서 사용 하더라도, 'app Directory'의 사용은 많은 편의성을 제공하고 있습니다.
빨리 정식 릴리즈가 되었으면 좋겠습니다.