https://www.youtube.com/watch?v=iDHCQyTClk4
안녕하세요 단테입니다.
오늘은 바로 어제 일자인 2023.04.07에 출시된 13.3 버전에 추가된 기능에 대해 이야기해보겠습니다.
Next.js 13.2 에서 추가된 Metadata API는 title, meta, link 태그를 정적 혹은 동적으로 설정할 수 있게 해주었는데요
13.2버전에서 정적 메타데이터를 정의하기 위해서는 Metadata object 타입의 변수를 export 하면 되었습니다.
// layout.js 혹은 page.js에서
export const metadata = {
title: 'Home',
};
// Output:
// <head>
// <title>Home</title>
// </head>
export default function Page() {}
// layout.js 혹은 page.js에서
export async function generateMetadata({ params, searchParams }) {
const product = await getProduct(params.id);
return { title: product.title };
}
// Output:
// <head>
// <title>My Unique Product</title>
// </head>
export default function Page() {}
13.2 에서 추가할 수 있었던 title, meta, link태그 말고도 아래와 같은 항목들을 추가적으로 주입할 수 있게 되었습니다.
- opengraph-image.(jpg|png|svg)
- twitter-image.(jpg|png|svg)
- favicon.ico
- icon.(ico|jpg|png|svg)
- sitemap.(xml|js|jsx|ts|tsx)
- robots.(txt|js|jsx|ts|tsx)
- manifest.(json|js|jsx|ts|tsx)
위 api에 대해 더 알고 싶다면 api doc을 참고하세요.
file-based metadata api를 이용해 아래와 같은 유즈케이스를 가정해본다면
앱 전체에 favicon을 적용하고
, /about 페이지에 OG 이미지를 설정
아래와 같이 파일을 배치할 수 있습니다.
Next.js는 파일 경로에 따라 자동으로 아래와 같이 태그를 주입해줍니다.
// Visiting "/"
<link rel="icon" href="<computedUrl>"/>
// Visiting "/about"
<link rel="icon" href="<computedUrl>"/>
<meta property="og:image" content="<computedUrl>" type="<computedType>" ... />
정적 파일을을 동적으로 만들 수도 있습니다. 사이트 맵을 예시로 들어 sitemap.xml
을 동적으로 생성하기 위해서는 sitemap.js
를 생성해 dynamic routes의 배열을 반환하게 하면 됩니다.
// app/sitemap.js
export default async function sitemap() {
const res = await fetch('https://.../posts');
const allPosts = await res.json();
const posts = allPosts.map((post) => ({
url: `https://acme.com/blog/${post.slug}`,
lastModified: post.publishedAt,
}));
const routes = ['', '/about', '/blog'].map((route) => ({
url: `https://acme.com${route}`,
lastModified: new Date().toISOString(),
}));
return [...routes, ...posts];
}
이번에 새롭게 추가된 file-based option을 통해 정적/동적으로 메타데이터를 설정할 수 있게 되었습니다. Metadata API는 App Router(app)에서 사용이 가능하며 pages 디렉토리에서는 사용이 불가능합니다.
지난 Next.js conf 발표일로부터 벌써 반년이라는 시간이 흘렀네요.
콘퍼런스에서 소개된 @vercel/og와 Satori를 통해 jsx, html, css를 이용해 동적으로 이미지를 생성할 수 있게 되었습니다.
@vercel/og를 통해 온라인으로 동적 생성된 참가자 티켓들만 100,000장이라고 하는데요,
next/server
의 ImageResponse
를 통해 이미지를 생성할 수 있게 되었습니다.
// /app/about/opengraph-image.tsx
import { ImageResponse } from 'next/server';
export const size = { width: 1200, height: 600 };
export const alt = 'About Acme';
export const contentType = 'image/png';
export default function og() {
return new ImageResponse();
// ...
}
ImageResponse api는 Next.js에서 제공하는 다른 api와 함께 사용할 수 있습니다. 예를 들어 앞서 소개한 file-based Metadata와 결합해 opengraph-image.tsxㅍ ㅏ일에서 request time혹은 build 타임에 og image, 혹은 twitter image를 생성할 수 있습니다.
이번 버전에 추가된 기능 중 제일 재미있는 기능입니다.
CSR만 수행하는 SPA의 경우 next.js를 사용하기보다는 Vite나 CRA를 사용하고 nginx, apache와 같은 웹서버를 통해 static hosting을 했습니다.
그런데 Vite CRA를 사용헀을 때의 문제점은 생성되는 정적 html이 index.html 오직 한개이고 코드로 얼마나 많은 페이지를 만든 것과 관계 없이 항상 이 빈 index.html을 최초 렌더링시 서빙해야 한다는 점입니다.
서빙해야 하는 페이지가 늘어날 수록 유저가 다운로드 받아야하는 js bundle의 사이즈도 커지고 Lazy로딩을 한다면 폭포수 문제는 피할 수 없습니다.
각 페이지 혹은 유저 인터렉션으로 발생하는 js 번들 폭포수 문제
리엑트 코어 개발팀인 페이스북의 Dan abramov는 해당 문제에 대해 gist 문서를 열고 이부분에 대해 다른 개발자들과 더 나은 방법이 없는지 의견을 주고받았었는데요, 시간이 되시면 한번 읽어보세요.
next build
Next.js에서 빌드 이후 생성된 파일을 살펴보면 아래와 같은데요
out/index.html
out/404.html
out/stuff/[id].html
개발자가 작성한 url 엔트리 포인트에 맞게 각각의 html 파일이 생성되었습니다.
위에서 말한 CSR SPA의 문제해결점으로는 next build를 통해 각 route별로 만들어지는 정적 html파일들을 각 static hosting 웹서버의 문법에 맞게 경로를 rewrite해서 제공하는 것입니다.
rewrite / to out/index.html
rewrite /stuff/whatever to out/stuff/[id].html
웹서버에서 사용하는 문법이 다르고 각 선언문을 일일이 작성해줘야 하여 next.js만으로 자동으로 rewrite을 해결해주지 않는다고 볼 수 있는데요 이번 버전에 해당 문제를 해결해주는 기능이 추가된 것입니다.
이렇게 route별로 빌드된 정적파일을 제공함으로 인해 브라우저에서 불필요한 파일을 다운로드하지 않을 수 있습니다.
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
output: 'export',
};
module.exports = nextConfig;
이 기능은 슬롯을 통해 동일한 라우트에서 동일한 뷰를 보여주는 기능입니다.
slot
이라는 새로운 개념이 등장했습니다. 이 슬롯은 @folder
폴더를 선언함으로 만들수 있습니다.
slot은 layout 에서 props로 받을 수 있는 것으로 위와 같은 디렉토리 구조에서 slot은 user, team
입니다.
// app/dashboard/layout.js
export default async function Layout({ children, user, team }) {
const userType = getCurrentUserType();
return (
<>
{userType === 'user' ? user : team}
{children}
</>
);
}
위 예제에서 조건부 렌더링을 통해 userType의 값에 따라 다른 슬롯을 렌더링 합니다.
여기서 children은 page.js 에서 작성한 내용으로 dashboard/page.js
은 곧 dashboard/@children/page.js
라는 슬롯을 생성한 것과 동일합니다.
이전 페이지의 중요한 context를 유지한 상태로 새로운 route의 페이지를 렌더링하게 해줍니다.
(..)
컨벤션을 이용하는데 이 컨벤션은 이전 디렉토리를 참조할 때 사용하는 ../.
참조문법과 유사하다고 볼 수 있습니다. app
directory를 참조하기 위해서는 (...)
를 사용합니다.
좀 별론데?
웹에서 모달을 사용할 떄 아래와 같은 이슈가 있습니다.
Intercepting route는 URL을 통해 모달 context를 공유할 수 있게 하며 페이지를 새로고침할 때 modal context가 사라지는 이슈를 해결합니다.
아래와 같이 Parallel Routes와 함께 사용한다면 photo를 눌렀을때 /photo/123
이라는 전용 route로 클라이언트 사이드 네비게이션이 되며 모달이 열립니다.
feed
├── @modal
│ └── (..)photo
│ └── [id]
│ └── page.tsx
├── page.tsx
└── layout.tsx
photo
└── [id]
└── page.tsx
마치 특정 컴포넌트의 콘텍스트를 하나의 페이지 route와 동일한 레이어로 생각해 렌더링할 수 있다
로 이해하면 될 것 같습니다.
오늘은 Next.js 13.3 버전에 대해 알아보았습니다.
감사합니다.