[React🌀] Next JS 13 업데이트 사항 총 정리 📄
들어가며
안녕하세요. 오랜만에 포스팅을 하네요. 😃
사실 react 와 svelte 를 비교하는 글을 준비하고 있었는데, 아무래도 제가 svelte에 관한 내용을 쓰기에는 사용경험이 너무 적더라구요.
이미 관련된 좋은 글도 너무 많은데 차별성도 없고 내용도 결국 똑같아지는 것 같고.. 몇 가지 개인적인 일들이 있기도 했구요.
아무튼 군소리는 여기까지 하고, 바로 시작하겠습니다!
이 포스팅의 내용들은 99% 이상이 아래 링크된 Next JS의 공식 블로그 내용을 바탕으로 하고 있을 테니, 영어에 자신 있는 분들은 그냥 블로그 글 읽는게 더 도움이 되실 수도 있습니다 😄
Next JS 13
Next JS 12가 공개된지 1년쯤 되었네요 벌써 .. 드디어 Next js 13이 공개되었습니다!
https://programming119.tistory.com/255
Next JS가 react 기반의 메인 프레임워크 자리에서 내려오지 않고 있는 것에 너무 감사합니다. Remix나 svelete 같은 친구들이 자리를 위협하고 있지만, 아직까지는 Next JS를 따라오지는 못하죠~
Vercel은 꾸준하게 발전하고, 변화하려고 노력하는 것 같아요. 정말 멋진 것 같습니다. Turborepo 도 좋고, 문서도 꾸준히 업데이트 하면서도 blog 글도 꾸준히 올라오구요. 심지어 이번 next JS 13 공개는 Conference 형식으로 진행됐습니다.
깔쌈하죠잉?
https://nextjs.org/conf?utm_source=next-site&utm_medium=web&utm_campaign=conf-takeover
Next JS 13에는 어떤 변화가?
next js 13에서 바뀌는 변경사항들을 크게 분류해보면 위와 같습니다.
1. app 디렉토리 (디렉토리 구조 변화)
2. Turbopack 적용
3. 강화된 next/image
4. 새로운 @next/font
5. 강화된 next/link
아래 3개는 메이저 버전의 변경사항이라기 보다는 기존 기능의 업그레이드라고 생각하면 될 것 같고,
중요한 부분은 1번과 2번이죠. app/ 기반 디렉토리 구조로의 변경, 그리고 Turbopack 도입 입니다.
하나씩 조금 더 자세히 살펴보시죠.
app/ Directory (beta)
초반에 Next JS 에서 가장 각광받았던 것은 next js 만의 파일 시스템 구조였죠. pages 하위에 페이지들을 구성하고, _app 과 _document 등을 이용해서 해당 파일들의 레이아웃들을 잡아주었습니다. 하지만 이제는 pages 가 없어집니다!! 😮
정확히 말하자면 pages 대신 app 디렉토리가 베이스 디렉토리가 됩니다. 이는 React Server Components 와 새롭게 생기는 Data fetching 기능, 그리고 Streaming등을 유연하게 지원하게 하기 위함이라네요. 하지만 디렉토리를 한번에 바꾸는 것은 대규모 프로젝트에서는 꽤나 리스키할 수 있죠.. pages 기반의 구조도 당분간은 동시 지원해줄 것 같습니다.
자세한 내용은 제가 따로 정리해둔 글이 있으니 참조해주십쇼.
https://programming119.tistory.com/267
Data Fetching
그리고 이것은 앞서 변경사항 목록에 없는 내용인데요, next js 에서 지원하는 SSR data fetching 방식이 바뀝니다. (꽤나 중요한 내용같은데 왜 목록에 포함이 안되어있는지는 모르겠네용)
getServersideProps, getStaticProps, getInitialProps 모두 next js 가 SSR 과 CSR 을 자유롭게 넘나드면서 사용할 수 있게 해주는 키 포인트였었죠. 헌데, 이 방식도 바뀌나 봅니다.
// app/page.js
async function getData() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can return Date, Map, Set, etc.
return res.json();
}
// This is an async Server Component
export default async function Page() {
const data = await getData();
return <main>{/* ... */}</main>;
}
기존에는 useEffect 에서 data를 fetch해 오고, 그 데이터를 특정 state에 저장하는 방식을 많이 사용했죠.
헌데 위 방식을 보시면 그냥 컴퍼넌트 로직에서 바로 await으로 data 를 가져오고 있고, 화면에 data의 내용을 그리네요? 그러면 일반적으로는 CSR로 동작한다고 생각하죠? 하지만 Next JS 13 에서 새로 지원하는 방식에서는 그렇지 않습니다. 위는 SSR 단계에서 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 } });
다음과 같이 fetch 에 3가지 옵션이 도입됐습니다.
- { cache: 'force-cache' } : build시에 가져온 데이터가 사용되고 직접 revalidate하기 전까지는 데이터가 캐싱되는 방식. getStaticProps와 같습니다. 그리고 이 옵션은 fetch 의 default 옵션이므로 위 예시도 이 방식으로 적용되겠죠.
- { cache: 'no-store' } : 매 요청시마다 새로 fetch하게 됩니다. 서버 단에서 말이죠. getServerSideProps와 유사합니다.
- { next: { revalidate: 10 } } : next 옵션을 줌으로서, cache 돼고있는 data 들을 revalidate 시킬 수 있습니다. 값은 초 단위를 의미하며, 이 예시에서는 10을 줬으니 10초마다 캐시가 지워질 것이고 새롭게 갱신된 데이터로 새로 캐싱되겠네요.
이 방식이 가능해진 이유도, 서버 컴퍼넌트와 클라이언트 컴퍼넌트가 나뉘었기 때문이죠. 이 구조에 적응만 한다면 한결 편해질 것 같습니다.😀
아래 더 자세한 내용이 담겨있습니다.
https://beta.nextjs.org/docs/data-fetching/fundamentals
Turbopack
Next JS 에서 Rust 기반의 강화된 Webpack인 Turbopack을 공개했습니다. 아직은 alpha 단계라고 하지만, Next JS 13 에서는 이를 기본 번들러로 사용할 것인가 봐요.
매우 빠르다고 자랑하고 있는데 데이터만 보면 드라마틱하게 빨라지긴 했네요. 근데 전에도 swc 컴파일러로 바꾸고 빌드 빨라졌다고 했는데, 막상 사내 코드로 테스트해보니까 차이 거의 없었다는게 ..... -_-;; 지켜봐야할 것 같습니다. 이번엔 더 체감됐으면 좋겠네요.
conf 내용에서는 그냥 빨라진 feature를 자랑만 하고 있고 딱히 관련 내용을 더 다루지는 않았네요. 아래 링크에는 터보팩에 대한 내용이 더 담겨있습니다.
https://turbo.build/pack/docs/features
next/image
웹에서 이미지의 역할은 날이 갈수록 중요해지고 있습니다. 솔직히 3줄 이상 줄글이 쓰여져있으면 읽기 싫죠.. (제 글도 휙휙 넘기면서 코드랑 사진 부분만 보고계신거 다 압니다 ㅋㅋ)
Next JS 에서도 기존에 지원하던 next/image 를 강화했다고 하네요. 아래와 같은 특징이 있답니다.
- 줄어든 client-side JavaScript
- 스타일링과 설정이 더 편해짐
- alt 태그가 필수
- 네이티브한 lazy 로딩은 hydartion이 필요없기 때문에 더 빠름
import Image from 'next/image';
import avatar from './lee.png';
function Home() {
// "alt" is now required for improved accessibility
// optional: image files can be colocated inside the app/ directory
return <Image alt="leeerob" src={avatar} placeholder="blur" />;
}
⚠️ 기존에 사용되던 next/image 는 next/legacy/image path로 import해야된다고 합니다.
그리고 이번 conf에서는 소개되지 않았지만, 제가 이번에 vercle blog 읽으면서 알게된 몇 가지 next/image 의 장점을 소개해드리고자 합니다.
CLS 방지
Next.js will automatically determine the width and height of your image based on the imported file. These values are used to prevent Cumulative Layout Shift while your image is loading.
요 이미지 컴퍼넌트가 참 좋은게, 자동으로 width 랑 height 정보를 받아와서, CLS 를 막아주는 점인 것 같습니다.
import Image from 'next/image'
export default function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
/>
<p>Welcome to my homepage!</p>
</>
)
}
하지만, 로컬 이미지 파일이 아닌 remote URL 기반이라면, 반드시 width와 height을 명시해주어야 합니다.
Priority
Next JS Image 컴퍼넌트에는 priority 속성이 있는데요. LCP(Largest Contentful Paint) 를 줄일 수 있게 도와주는 기능입니다.
쉽게 말하자면, 사용자 화면에 모든 UI 렌더링되는 최종 시간을 줄일 수 있다는 뜻입니다.
이미지 용량이 커서, 렌더링 저하를 유발시킬 것 같은 이미지 컴퍼넌트에 아래와 같이 priority 속성을 줌으로서, next js 가 화면을 로딩할 때 해당 이미지 렌더링에 우선순위를 줘고 최종적으로 LCP를 줄일 수 있다고 합니다.
import Image from 'next/image'
export default function Home() {
return (
<>
<h1>My Homepage</h1>
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={500}
priority
/>
<p>Welcome to my homepage!</p>
</>
)
}
@next/font
요것도 꽤나 재밌는 변경사항인데요. 새로운 폰트 시스템을 제공한답니다!
- 폰트 자동 최적화
- 폰트 성능을 위해 불필요한 추가 request를 제거
- Built-in automatic self-hosting for any font file
- size-adjust 옵션을 이용한 layout shift 제거
우선 이 @next/font 는 구글 폰트를 기본적으로 지원합니다.
놀라운 점은 CSS와 font는 빌드 타임에 정적 자원으로 불러와지기 때문에 실제 브라우저에서는 google로 요청을 보낼일이 전혀 없다고 하네요.
import { Inter } from '@next/font/google';
const inter = Inter();
<html className={inter.className}>
아래와 같이 커스텀 폰트도 지원을 하구요, 이 또한 캐싱과 preloading을 지원한답니다.
import localFont from '@next/font/local';
const myFont = localFont({ src: './my-font.woff2' });
<html className={myFont.className}>
https://nextjs.org/docs/basic-features/font-optimization
next/link
이 부분은 사소한 변경사항인데요, Link 컴퍼넌트를 사용할 때, 드디어 a 태그를 껴넣지 않아도 된다고 합니다.
이 부분은 예전부터 항상 개선됐으면 하던 점인데 드디어 됐군요 ㅎㅎㅎ
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>
⚠️ 오히려, 이제는 더이상 <a> 태그를 <Link> 안에 포함시킬 수 없다고 하네요!
Open Graph Image Generation
이 부분을 이해하려면, 우선 OG 에 대해 알고계셔야 합니다.
https://blog.ab180.co/posts/open-graph-as-a-website-preview
next js 13버전부터는 이 OG 태그용 이미지 generation도 공식적으로 지원하나봅니다. vercel에서 만든 @vercel/og 라는 모듈을 사용하면 되는데요. 아래와 같은 예시를 들고 있습니다.
pages/api/og.tsx (app/api/og.tsx)
import { ImageResponse } from '@vercel/og';
export const config = {
runtime: 'experimental-edge',
};
export default function () {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello world!
</div>
),
{
width: 1200,
height: 600,
},
);
}
위와 같이 og 이미지를 반환하는 용도의 next js api 파일을 만들어줍니다. @vercel/og 모듈 내부의 ImageResponse를 이용해서 구현할 수 있습니다.
그러면 이제 해당 api 를 요청했을 때 (이 케이스에서의 path는 http://localhost:3000/api/og 겠죠?) 아래와 같은 결과를 얻을 수 있을겁니다.
이제 og 태그를 실제 페이지에 적용해야겠죠?
해당 og 이미지를 적용할 next JS 페이지 컴퍼넌트로 가서 og:image를 갖는 메타 태그를 생성해줍니다. 그리고, 위에 og image를 반환하는 api path를 content 속성에 넣어주면 끝입니다!
<head>
<title>Hello world</title>
<meta
property="og:image"
content="https://og-examples.vercel.sh/api/static"
/>
</head>
이제 킹갓 Next JS 에서 지원하지 않는 fe 웹기능은 없는 듯 하옵니다..
https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation#
Middleware API Updates
Next JS 12 부터 생긴 기능인 middleware에도 업데이트 내용이 생겼네요.
첫 번째는 request 에 헤더를 set 하기 한결 쉬워졌구요,
// 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 없이도 바로 특정 status를 response할 수 있습니다.
// 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 JS 13 conf를 살펴봤습니다. 이번 conf 영상이 너무 퀄리티가 좋아서, 직접 시청해보는 것을 추천드립니다. youtube에도 업로드 되어있어요!
그리고 벌써 Next JS 가 공개된지도 6년이 되었다고 하네요.. 시간이 참 빠르죠 하하 😅
저도 어느덧 입사한지 2년이 다 되어 간답니다. 블로그 업로드 주기도 뜸해지고, 개발자로써 성장하고자 하는 열정이 벌써부터 예전같지 않다는게 한편으로는 씁쓸하네요... 제가 더 행복하게 잘 살기 위해서 시기상으로 지금은 개발보다 중요한게 많다고 느껴져서 그런 것 같아요.
그래도 성장하려는 의지는 잠시 줄어들 수 있을지라도 은퇴하는 그날까지 성장에 대한 욕구는 내려놓지 않으려합니다. 조금씩이라도 발전하는 사람이 되어야죠 ㅎㅎ 모두들 힘내시고 즐코합시다!
항상 감사합니다~