Post

Next.js (14. Metadata and OG images) 정리

Next.js (14. Metadata and OG images) 정리

0. 메타데이터와 OG 이미지


Metadata API는 SEO와 소셜 공유를 위한 메타데이터를 정의하는 세 가지 방식을 제공한다.

  • 정적 metadata 객체:
    • layout.tsx 또는 page.tsx에서 metadata를 export한다.
  • 동적 generateMetadata 함수:
    • 데이터에 의존하는 메타데이터를 fetch해 반환한다.
  • 파일 기반:
    • 특수 파일명(favicon, OG 이미지 등)으로 정적 또는 동적 메타데이터를 추가한다.

metadata 객체와 generateMetadata 함수는 Server Component에서만 사용할 수 있다.
Next.js는 설정된 메타데이터를 기반으로 관련 <head> 태그를 자동으로 생성한다.

1. 메타데이터 설정


1-1. 기본 메타 태그


라우트에 별도의 메타데이터를 정의하지 않아도 항상 추가되는 기본 태그가 두 가지 있다.

1
2
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
태그역할한 줄 설명
charset문자 인코딩 설정글자가 깨지지 않게 함
viewport화면 크기/비율 설정모바일 화면에 맞게 조정

1-2. 정적 메타데이터


layout.tsx 또는 page.tsx에서 Metadata 객체를 export해 정적 메타데이터를 정의한다.

1
2
3
4
5
6
7
8
9
10
// app/blog/layout.tsx

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Blog',
  description: '...',
}

export default function Layout() {}

1-3. 동적 메타데이터 — generateMetadata


데이터에 의존하는 메타데이터는 generateMetadata 함수로 동적으로 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// app/blog/[slug]/page.tsx

import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: Promise<{ slug: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const slug = (await params).slug

  // 포스트 정보 페칭
  const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
    res.json()
  )

  return {
    title: post.title,
    description: post.description,
  }
}

export default function Page({ params, searchParams }: Props) {}

2. generateMetadata 활용 패턴


2-1. 스트리밍 메타데이터


동적 렌더링 페이지에서 Next.js는 메타데이터를 별도로 스트리밍해 UI 렌더링을 차단하지 않는다. generateMetadata가 완료되면 HTML에 주입되므로 시각적 콘텐츠를 먼저 스트리밍할 수 있다.

Twitterbot, Slackbot, Bingbot 같은 봇과 크롤러에는 스트리밍 메타데이터가 비활성화된다. 이들은 <head> 태그에 메타데이터가 포함된 HTML을 요구하며, User Agent 헤더로 감지된다. 프리렌더링된 페이지는 빌드 타임에 메타데이터가 결정되므로 스트리밍을 사용하지 않는다.

2-2. 데이터 메모이제이션 — React cache


메타데이터와 페이지 모두에서 동일한 데이터를 중복 없이 한 번만 fetch하려면 React cache를 사용한다.

1
2
3
4
5
6
7
8
9
10
// app/lib/data.ts

import { cache } from 'react'
import { db } from '@/app/lib/db'

// getPost는 두 번 사용되지만 한 번만 실행된다
export const getPost = cache(async (slug: string) => {
  const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
  return res
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// app/blog/[slug]/page.tsx

import { getPost } from '@/app/lib/data'

export async function generateMetadata({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.description,
  }
}

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  return <div>{post.title}</div>
}

3. OG 이미지


3-1. 파일 기반 메타데이터 종류


특수 파일명을 사용해 메타데이터를 정적 또는 동적으로 추가할 수 있다.

파일설명
favicon.ico, apple-icon.jpg, icon.jpg파비콘 및 앱 아이콘
opengraph-image.jpg, twitter-image.jpgOG 이미지
robots.txt크롤러 접근 규칙
sitemap.xml사이트맵

3-2. 정적 OG 이미지


app 폴더 루트에 opengraph-image.jpg 파일을 추가하면 전체 사이트에 적용되는 정적 OG 이미지가 설정된다.

OG image special file inside the App folder with sibling layout and page files

특정 라우트에만 적용하려면 해당 폴더 안에 파일을 추가한다. 더 구체적인 경로의 이미지가 상위 폴더의 이미지보다 우선 적용된다.

OG image special file inside the blog folder

3-3. 동적 OG 이미지 — ImageResponse


ImageResponse를 사용하면 JSX와 CSS로 동적 OG 이미지를 생성할 수 있다. 데이터에 따라 달라지는 OG 이미지가 필요할 때 유용하다.

blog 폴더 안에 opengraph-image.tsx 파일을 추가하고 next/og에서 ImageResponse를 import한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// app/blog/[slug]/opengraph-image.tsx

import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'

// 이미지 메타데이터
export const size = {
  width: 1200,
  height: 630,
}

export const contentType = 'image/png'

// 이미지 생성
export default async function Image({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)

  return new ImageResponse(
    (
      // ImageResponse JSX 요소 
      <div 
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        {post.title}
      </div>
    )
  )
}

ImageResponse는 flexbox와 절대 위치 지정, 커스텀 폰트, 텍스트 줄바꿈, 중앙 정렬, 중첩 이미지를 지원한다. display: grid 같은 고급 레이아웃은 지원하지 않는다.

This post is licensed under CC BY 4.0 by the author.