Next.js 14 App Router에서 dynamic metadata를 활용해 동적 페이지 SEO 점수 끌어올리기
모던 웹 서비스 개발에서 검색엔진 최적화(SEO)는 서비스의 인지도와 오가닉 트래픽을 결정하는 가장 중대한 작업 중 하나입니다. 특히 Next.js를 도입하는 주된 요인 중 하나가 바로 서버 사이드 렌더링(SSR)을 통한 강력한 SEO 지원입니다.
Next.js 14 App Router 환경에서, 각 상세 페이지나 블로그 포스트의 데이터베이스 값을 기반으로 유연하게 메타 데이터를 주입해 검색 노출 점수를 극대화할 수 있는 Dynamic Metadata(동적 메타데이터) 구현 가이드를 실제 코드 템플릿과 함께 소개합니다.
1. Static Metadata vs Dynamic Metadata
Next.js 14에서는 파일 시스템 기반 라우팅의 page.tsx 또는 layout.tsx 파일 내에서 간단하게 SEO 메타데이터를 선언할 수 있습니다.
- 정적 메타데이터 (Static Metadata): 고정된 페이지만 지원할 때 사용하며, 단순히
metadata상수를 export하는 방식입니다. - 동적 메타데이터 (Dynamic Metadata): 게시글 ID나 카테고리 쿼리에 따라 메타 타이틀, 설명, 오픈 그래프(Open Graph) 이미지가 변경되어야 할 때 사용하며,
generateMetadata비동기 함수를 제공해야 합니다.
2. Dynamic Metadata 구현하기: generateMetadata API
블로그 포스트 상세 페이지(/posts/[slug])를 기준으로 동적 메타데이터를 설정하는 표준 코드 템플릿입니다.
💡 실무 코드 예시:
app/posts/[slug]/page.tsx 파일 내에 다음과 같이 구성합니다.
import { Metadata, ResolvingMetadata } from 'next';
interface Props {
params: { slug: string };
searchParams: { [key: string]: string | string[] | undefined };
}
// 1. 동적으로 메타데이터를 생성하는 generateMetadata 함수 선언
export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
// slug 파라미터를 이용해 외부 API나 DB에서 포스트 정보 조회
const slug = params.slug;
const post = await fetch(`https://api.myblog.com/posts/${slug}`).then((res) => res.json());
// (선택사항) 부모 레이아웃의 메타데이터를 상속받거나 참고할 수 있습니다.
const previousImages = (await parent).openGraph?.images || [];
return {
title: `${post.title} | Goldenwhale Blog`,
description: post.excerpt || "유용한 지식과 자기계발 정보를 전합니다.",
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://myblog.com/posts/${slug}`,
siteName: 'Goldenwhale Blog',
images: [
{
url: post.coverImage || '/images/default-thumbnail.jpg',
width: 800,
height: 600,
},
...previousImages,
],
locale: 'ko_KR',
type: 'article',
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage || '/images/default-thumbnail.jpg'],
},
alternates: {
canonical: `https://myblog.com/posts/${slug}`,
},
};
}
// 2. 실제 페이지 컴포넌트 렌더링
export default function Page({ params }: Props) {
return (
<article className="max-w-2xl mx-auto py-8">
{/* 본문 콘텐츠 렌더링 영역 */}
<h1 className="text-3xl font-bold">{params.slug} 포스트 내용</h1>
</article>
);
}
3. SEO 점수를 추가로 높여주는 3가지 실무 최적화 팁
① 중복 호출 방지를 위한 fetch 자동 메모이제이션
generateMetadata 함수 내부와 실제 Page 컴포넌트 내부에서 동일한 포스트 데이터를 조회하기 위해 fetch 함수를 각각 호출할 수 있습니다.
Next.js 14는 동일한 URL에 대한 fetch 요청을 자동으로 메모이제이션(Memoization)하여 캐싱하므로, 데이터베이스나 API 서버에 중복으로 쿼리가 날아가는 성능 저하 우려 없이 안심하고 호출해도 괜찮습니다. (단, Axios나 직접 구현한 DB 클라이언트를 사용하는 경우에는 react 패키지의 cache 함수를 수동으로 감싸 주어야 합니다.)
② Canonical URL(대표 주소) 필수 지정
검색엔진 크롤러가 동일한 포스트에 여러 개의 중복 URL(예: 파라미터가 붙은 임시 주소 등)로 진입할 때 점수가 깎이는 페널티를 방지하기 위해, alternates.canonical에 표준 주소를 동적으로 바인딩해 주는 것이 필수적입니다.
③ Open Graph 이미지의 종횡비 조절
카카오톡이나 페이스북, 트위터에 블로그 링크가 공유되었을 때 썸네일 이미지가 잘리지 않으려면, 오픈 그래프 이미지 크기를 1200 x 630 픽셀이나 1.91:1 비율에 맞춰 동적으로 매칭되도록 가공해야 최고의 시각적 효과를 볼 수 있습니다.
✍️ 글을 마치며
Next.js 14의 App Router는 강력해진 데이터 페칭 모델과 결합하여 동적 메타데이터를 매우 단순하고 안전하게 구성할 수 있는 최적의 개발 경험을 선사합니다.
이 가이드를 바탕으로 세련된 블로그 포스트마다 매끄러운 타이틀과 소셜 썸네일 이미지를 동적으로 제공하여 네이버, 구글 크롤러의 상위 랭킹을 정복해 보시기 바랍니다.