[Next.js] Next.js 13 변경사항
🤍 Next.js 13
Next.js 12버전도 2021년 중순 업데이트 된 것으로 기억하는데, Next.js 13이 2022년 10월에 컨퍼런스에서 다시 발표됬다.
체감상 가장 큰 변화를 느낄 것 같은 버전 체인지이다. (디렉토리 구조, getServerSideProps 등 Next 정체성이 대거 바뀜)
Next 개발팀은 13버전 업을 통해 좀 더 제한없이 동적으로 동작하는 기반을 마련하고자 했다는게 주요 컨셉이다.
- 주요 변경사항
- app/Directory(beta) : 쉽고, 빠르고, 적은 클라이언트 JS
- Layouts(레이아웃)
- React Server Components (리액트 서버 컴포넌트)
- Streaming (스트리밍)
- Turbopack(alpha) : Rust 기반. Webpack 보다 700배, Vite보다 10배 빠른 번들러
- 새로운 next/image(stable) : 더 빨라진 Lazy Loading
- 새로운 @next/font(beta) : 구글 폰트 자체 내장
- 향상된 next/link(stable) : <a> 태그를 자식 요소로 넣을 필요 없음
- 설치 커멘드
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
위 커멘드로 설치하며, pages 디렉토리 구조가 아직 배포에 적합한 형태라고 한다.
🤍 Next.js 13 변경사항
그렇다면, 위에서 나열한 주요 변경사항들을 하나씩 자세히 알아보았다.
1. app/Directory : 새로운 디렉토리 구조 - beta
기존 Next에선 pages/ 디렉토리 내에 폴더, 파일을 생성하여 라우팅을 설정할 수 있었다.
Next.js 13부터는 app/ 이라는 새로운 디렉토리로 라우팅을 설정할 수 있으며,
라우팅 환경개선 뿐만 아니라 레이아웃, 서버 컴포넌트, 스트리밍, 데이터 패칭까지도 지원하는 형태로 향상시켰다.
app 폴더로 기존 파일 시스템 라우팅을 구현할 수 있으며, 여기의 page.js가 해당 경로(라우팅)의 페이지 컴포넌트가 된다.
(마이그레이션을 위해 기존 pages도 지원중)
1) Layouts
공통적인 UI를 children을 감싸는 랩핑 컴포넌트 형태로 쉽게 제공하는 형태이다.
이를 통해, 공통 레이아웃의 상태를 유지하고, 불필요한 리렌더링을 방지하며, 컴포넌트 간 상호작용을 향상시켰다.
폴더 경로 안에, layout.js 컴포넌트만 추가해주면 쉽게 공통 레이아웃을 적용할 수 있다.
(바로 적용하면 경고가 뜬다. next.config.js > experimental 설정에 appDir: true를 추가해야 한다.)
* 예시코드
app 폴더에 layout.tsx, page.tsx 2개 파일을 둔 예시코드이다.
아래 사진처럼, layout은 공통적으로 적용되고, 그 자식 컴포넌트로 page.tsx 컴포넌트가 들어가는 형태이다.
2) React Server Component
app 파일구조는 React의 Server Components 아키텍쳐를 지원한다. app 디렉토리 내 파일은 디폴트로 서버 컴포넌트다.
서버 컴포넌트는 클라이언트에 전송되는 JS 코드량을 줄여서 초기 페이지 로드가 빨라진다.
또한, route가 로드되면 Next.js와 React 런타임이 각각 로드된다.(이는 캐싱되고 예측가능한 사이즈)
이는 어플리케이션이 커지더라도 런타임 크기가 증가하지 않으며, 추가적인 JS기능은 클라이언트 컴포넌트로 부여한다고 한다. (약간 모호)
* 클라이언트 컴포넌트
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked the Count++ button {count} times</p>
<button onClick={() => setCount(count + 1)}>Count++</button>
</div>
);
}
클라이언트 컴포넌트를 사용하려면 "use client" 를 최상단에 적어준다. 아래 경우에서는, 반드시 클라이언트 컴포넌트를 써야 한다.
- useState, useEffect 훅을 사용하는 경우
- 특정 브라우저 API에 의존성이 있는 경우
- 특정 Event Listeners를 추가하는 경우
3) Streaming
app 파일구조는 렌더링되는 UI단위를 점진적으로 렌더링하고 스트리밍하는 기능을 가능케 한다.
Next.js의 레이아웃과 서버 컴포넌트를 사용하면, 데이터가 필요하지 않은 일부는 즉시 렌더링하고, 데이터를 가져오는 부분은 로딩을 적용할 수 있다.
(로딩처리는 디렉토리 경로 내의 loading.tsx 혹은 <Suspense> 로 처리한다.)
4) Data Fetching
이 부분이, Next.js 13에서 가장 크게 변경된 사항이 아닐까 느낀다!
fetch() Web API를 사용할 수 있게 되면서, 이제는 컴포넌트 레벨에서도 SSR의 적용이 가능하다.
- Next.js 컴포넌트들은 기본적으로 서버 컴포넌트이므로, 서버단에서 데이터를 불러온다
- 데이터는 병렬적으로 일어난다. (각각의 패칭이 비동기 chaining으로 일어나지 않음)
- 데이터 패치는 자동으로 중복제거(deduping) 된다. 부모-자식 간의 동일한 요청들은 1회로 실행한다.
- Loading UI(혹은 Suspense), Streaming 등을 통해 데이터를 불러오는 영역과 미리 보여주는 영역을 나누어서 표현한다.
React는 fetch() API의 중복제거를 제공하며, Next.js는 캐싱과 재요청 처리까지 지원하려고 한다.
이를 통해, 기존의 SSG, SSR, ISR(Incremental Static Regeneration) 등의 기법을 아래와 같은 fetch() 옵션으로 대체 가능하다.
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
fetch(URL, { cache: 'force-cache' });
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
fetch(URL, { cache: 'no-store' });
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
fetch(URL, { next: { revalidate: 10 } });
2. Turbopack : 새로운 번들링 툴 - alpha
Next.js 13에서는 Rust 기반의 JS 번들링 툴인 Turbopack 이 포함되어있다. 기존 웹팩도 널리 사용됬지만, JS 기반인만큼 성능적 한계에 도달했다.
Next.js 12부터 Rust 기반 툴링으로 전환했고, 현재는 더 빠른 트렌스파일링과 번들링 성능을 보여주고 있다.
- Webpack 보다 700배 빠른 업데이트
- Vite 보다 10배 빠른 업데이트
- Webpack 보다 4배 빠른 cold start(Single Page 표현을 위해 로컬에 데이터를 불러오고 HTML 기반으로 로드준비가 된 상태)
Turbopack은 개발에 필요한 최소한의 어셋만 번들링하여 시작 시간이 매우 빠르다. (Vite와 유사)
또한, Server Component, 타입스크립트, JSX, CSS 등 웹 개발에 필요한 주요 문법들을 지원한다.
현재는 next dev만 터보팩을 실험적으로 지원한다. 이를 실행하려면, next dev --turbo 옵션을 추가해주면 된다!
3. 새로운 next/image - stable
Next.js 13은 기존의 Image 컴포넌트에 좀 더 강력한 기능들을 부가해서,
layout 변경 없이 간단히 이미지를 표시하고 파일을 최적화하여 성능을 향상시켰다.
- 클라이언트 측에서 더 적은 양의 Javascript 코드를 가짐
- 더 쉽게 스타일링과 설정이 가능
- alt 속성을 필수로 제한하여 웹 접근성 향상
- 웹 플랫폼에 맞게 조정 (사이즈와 화질 등) - 이미지 로드가 느리면 기존 레이아웃이 밀리는 Layout Shift를 방지 (width, height 필수)
- hydration을 필요로 하지 않는 네이티브 lazy-loading 으로 속도 향상
* Next.js 13의 next/image 컴포넌트로 업그레이드
기존 이미지 컴포넌트가 next/legacy/image 로 변경되었다.
자동으로 이를 적용하려면 root에서 아래 커멘드를 실행하면 되고, 이를 ./pages 경로에 반영해준다.
npx @next/codemod next-image-to-legacy-image ./pages
4. 새로운 @next/font - beta
Next.js 13은 다음과 같은 새로운 폰트 시스템을 도입한다. 무엇보다 구글 폰트를 내장한 것이 획기적인 발전일 것이다.
- 커스텀 폰트를 포함하여 폰트를 자동으로 최적화
- 개인 정보 보호 및 성능 향상을 위한 외부 네트워크 요청 제거
- 모든 폰트 파일에 대한 내장형 자동 자체 호스팅 제공
- CSS size-adjust 속성을 적용하여 자동으로 레이아웃 이동이 발생하지 않음
이 새로운 폰트 시스템을 통해 성능 개선과 개인 정보를 보호하며 모든 Google 폰트를 편하게 관리할 수 있다.
CSS 및 폰트 파일은 빌드 시간에 다운로드되고 나머지 정적 asset과 함께 self-hosting 된다. (브라우저에서 Google로 요청 필요없음)
import { Inter } from '@next/font/google';
const inter = Inter();
<html className={inter.className}>
// 폰트 설정추가
import { Roboto } from '@next/font/google'
const roboto = Roboto({
weight: '400',
subsets: ['latin'],
})
export default function MyApp({ Component, pageProps }) {
return (
<main className={roboto.className}>
<Component {...pageProps} />
</main>
)
}
커스텀 폰트들도 자동 self-hosting, 캐싱, pre-loading 등이 제공된다.
import localFont from '@next/font/local';
const myFont = localFont({ src: './my-font.woff2' });
<html className={myFont.className}>
- 전역 설정
_app.js의 <head> 태그의 스타일로 추가하거나, next.config.js 설정파일에 폰트에 관한 설정을 전역으로 설정할 수 있다.
// 전역 폰트설정
import { Inter } from '@next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function MyApp({ Component, pageProps }) {
return (
<>
<style jsx global>{`
html {
font-family: ${inter.style.fontFamily};
}
`}</style>
<Component {...pageProps} />
</>
)
}
// 전역 폰트옵션 설정
module.exports = {
experimental: {
fontLoaders: [
{ loader: '@next/font/google', options: { subsets: ['latin'] } },
],
},
}
5. 향상된 next/link - stable
<Link> 컴포넌트에 더 이상 수동으로 <a> 태그를 하위에 추가할 필요가 없어졌다.
<Link>는 이제 기본값으로 <a> 태그를 렌더링하며, <a> 태그의 Props들을 정상적으로 전달할 수 있다.
import Link from 'next/link'
// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>` always renders `<a>`
<Link href="/about">
About
</Link>
* Next.js 13의 next/link 로 업데이트
Link 태그 역시 기존코드를 Next.js 13으로 업데이트하는 커멘드를 제공한다. 이를 root에서 실행하면, ./pages 경로에 반영해준다.
npx @next/codemod new-link ./pages
* 기존 사용하던 형태
기존엔 Styled-Components, SEO 등을 적용하기 위해 a태그를 내부에 넣고 passHref로 링크를 전달해야하는 번거로움이 존재
import Link from 'next/link'
import styled from 'styled-components'
// This creates a custom component that wraps an <a> tag
const RedLink = styled.a`
color: red;
`
function NavLink({ href, name }) {
return (
<Link href={href} passHref legacyBehavior>
<RedLink>{name}</RedLink>
</Link>
)
}
export default NavLink
6. OG 이미지 생성
카카오톡 등 다른 곳에 링크를 첨부하면 연관 이미지가 뜨는데, 이를 OG(Open Graph) Image 라고 하는 소셜 카드가 뜬다.
이는, 콘텐츠 클릭율을 높이기에(최대 40%) 많이 사용되지만, 정적 소셜카드는 시간 소비, 에러 이슈 등 유지보수가 까다로워 스킵되곤 한다.
Next.js 13에서는 이를 개선한 동적 소셜카드 생성 라이브러리인 @vercel/og 를 도입했다고 한다.
// pages/api/og.jsx
import { ImageResponse } from '@vercel/og';
export const config = {
runtime: 'experimental-edge',
};
export default function () {
return new ImageResponse(
(
<div
style={{
display: 'flex',
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
}}
>
Hello, World!
</div>
),
);
}
이는 기존 솔루션보다 5배 더 빠른 성능을 보여준다.
* Vercel Edge Functions, WebAssembly 및 HTML, CSS를 이미지로 변환하고, React 컴포넌트 추상화를 활용하기 위한 새로운 핵심 라이브러리를 사용
7. 미들웨어 API 수정
이전 Next.js 12에서 라우터와 유연성을 위해 미들웨어를 도입했다면, Next.js 13에선 기존에 도입된 미들웨어에서 일부 수정이 생겼다.
- Request Header를 쉽게 설정
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-version`
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-version', '13');
// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
});
// Set a new response header `x-version`
response.headers.set('x-version', '13');
return response;
}
- rewrite, redirect 없이 미들웨어에서 직접 리스폰스 제공
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { isAuthenticated } from '@lib/auth';
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
};
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return NextResponse.json(
{
success: false,
message: 'Auth failed',
},
{
status: 401,
},
);
}
}
단, 이는 next.config.js 설정파일에서, experimental > allowMiddlewareResponseBody 설정이 필요하다.
// next.config.js
const nextConfig = {
...
experimental: {
allowMiddlewareResponseBody: true
},
};
8. 주요 변경사항
- 최소 요구 React 버전이 17.0.2 ► 18.2.0으로 상향
- 12.X 버전 지원이 종료되며, 최소 요구 Node.js 버전이 12.22.0 ► 14.6.0 상향
- swcMinify 설정 기본값이 false ▶︎ true 변경
- next/image ► next/legacy/image, next/future/image ► next/image 마이그레이션
- next/link가 <a> 태그를 자식으로 가질 수 없음. legacyBehavior 옵션으로 기존코드를 유지할 수 있으나, 커멘드로 업데이트 권장
- User-Agent 가 봇이면 더 이상 경로들을 prefetch 하지 않음
- 지원 종료된 target 옵션(MiddlewareInServerlessTarget)이 next.config.js 에서 제거
- 지원 브라우저가 변경 : Chrome 64+, Edge 79+, Firefox 67+, Opera 51+, Safari 12+, IE(지원종료)
대부분의 기능은 향상 혹은 베타이기에 큰 차이를 못느끼겠지만,
Turbopack 으로 인한 성능 향상과 새로운 app 디렉토리 도입이 상당한 격변이 아닐까 생각된다. (많은 Next 프로젝트의 리팩토링이 예상된다)
📎 출처
- Next.js 공식 블로그 : https://nextjs.org/blog/next-13
- Next.js 공식문서 : https://beta.nextjs.org/docs/routing/fundamentals
- Next.js 13 번역 및 정리 : https://tech.wonderwall.kr/articles/Next.js%2013/
- Next.js 13 정리 : https://velog.io/@hang_kem_0531/Next.js-13%EC%9D%B4-%EB%82%98%EC%99%80%EB%B2%84%EB%A0%B8%EB%8B%A4
- Next.js 13 추가기능 : https://blog.logrocket.com/next-js-13-new-app-directory/