2023년 10월 26일에 Next.js 14 버전이 공개되었다. (13 버전 공부한지도 얼마 안됐는데..?)
Next.js는 React.js 기반의 프레임워크로, 한국뿐만 아니라 전 세계에서도 많은 인기를 끌고 있는 라이브러리이다.
간단히 Next.js에 대해 알아보고, 14버전의 주요 변화점을 정리해보자.
Next.js 공식 웹사이트에서는 Next.js를 “빠른 웹 애플리케이션을 제작할 수 있는 빌딩 블록을 제공하는 유연한 React 프레임워크”라고 정의하고 있다.
최신 애플리케이션 구축할때 고려해야할 몇가지 사항(Building Blocks)을 아래와 같이 정의하였고, 각 부분에 대해 직접 구축할지 아니면 다른 라이브러리를 사용할지 결정해야 한다.
Next.js는 React 프레임워크로서 다양한 기능을 제공하고 있는데, 그중 가장 대표적인 것이 바로 라우팅이다.
많은 프레임워크는 라우팅을 위해 디렉토리 구조와 파일 규칙을 강제한다. Next.js 역시 예전부터 라우팅을 위한 파일을 웹사이트 구조에 맞춰서 루트 디렉토리에 있는 pages
라는 폴더 내부에 생성하도록 하였다.
그러나 Next.js 13.4 부터 App router 가 기본 설정으로 적용되면서, 라우팅의 많은 규칙이 달라졌다.
_document
에 작성하고, 모든 페이지가 공유하는 로직은 _app
에 작성했던 것과 달리, App router 방식에서는 해당 파일이 사라지고 디렉토리 단위로 적용되는 layout이라는 개념이 생겼다.page
라는 이름을 갖도록 변경되었다.
이미지 출처 : https://yozm.wishket.com/magazine/detail/2324/
next/head
의 Head 태그도 사용할 수 없다.
이미지 출처 : https://yozm.wishket.com/magazine/detail/2324/
이미지 출처 https://yozm.wishket.com/magazine/detail/2324/
더 많은 업데이트 사항은 공식 문서에서 확인할 수 있다.
next dev
와 Next.js 의 다른 파트를 재정비하였다.next dev
테스트가 90%가 통과되었으므로, next dev –turbo
를 사용하면 더 빠르고 안정적인 성능을 경험할 수 있다.api/
디렉터리에 새 파일을 생성할 수 있다.아래와 같은 submit 파일명으로 API Router를 만들어 주고, 클라이언트 측에서 React 와 onSubmit
과 같은 이벤트 핸들러를 사용하면 API Route로 fetch
를 만들 수 있다.
pages/api/submit.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const data = req.body;
const id = await createItem(data);
res.status(200).json({ id });
}
pages/index.tsx
import { FormEvent } from 'react';
export default function Page() {
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
});
// Handle response if necessary
const data = await response.json();
// ...
}
return (
<form onSubmit={onSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}
‘use server’
를 함수나 파일에 작성해 두면, 함수 내용을 자동으로 서버 API로 만들어주고, 개발자는 유저에게 코드가 노출될 걱정 없이 자유롭게 데이터베이스를 관리할 수 있다.예를 들어, 이전 라우터 예제를 하나의 Page 파일로 단순화할 수 있다. 단순화된 코드이다.
app/page.tsx
export default function Page() {
async function create(formData: FormData) {
'use server';
const id = await createItem(formData);
}
return (
<form action={create}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
);
}
서버 작업은 전체 App Router 모델에 통합되어 있으며, 다음을 수행할 수 있다:
revalidatePath()
또는 revalidateTag()
를 사용하여 캐시된 데이터 재검증redirect()
를 통해 다른 경로로 리디렉션useOptimistic()
으로 최적화된 UI 업데이트 처리useFormState()
로 서버의 오류를 포착하고 표시useFormStatus()
로 브라우저에 로딩 상태 표시app/page.tsx
export default function Page() {
return (
<main>
<header>
<h1>My Store</h1>
<Suspense fallback={<CartSkeleton />}>
<ShoppingCart />
</Suspense>
</header>
<Banner />
<Suspense fallback={<ProductListSkeleton />}>
<Recommendations />
</Suspense>
<NewProducts />
</main>
);
}
<Suspense />
경계를 기반으로 정적인 셸(shell)을 생성한다.<main>
<header>
<h1>My Store</h1>
<div class="cart-skeleton">
<!-- Hole -->
</div>
</header>
<div class="banner" />
<div class="product-list-skeleton">
<!-- Hole -->
</div>
<section class="new-products" />
</main>
<ShoppingCart />
가 사용자 세션을 확인하기 위해 Cookie를 읽기 때문에, 이 컴포넌트는 정적 셸과 동일한 HTTP 요청의 일부로 스트리밍된다. 추가적인 네트워크 왕복이 필요하지 않다.app/cart.tsx
import { cookies } from 'next/headers'
export default function ShoppingCart() {
const cookieStore = cookies()
const session = cookieStore.get('session')
return ...
}
loading.js
를 사용하고 있는 경우 이는 암묵적인 Suspense 경계이므로 정적 셸을 생성하는 데 추가 변경이 필요하지 않다.💡 References