Next.js는 React 기반 프레임워크 중 가장 SEO 친화적인 선택입니다. 하지만 프레임워크가 SEO를 '가능하게' 해줄 뿐, 실제 최적화는 개발자의 몫입니다.
이 가이드에서는 Next.js 15 App Router 기준으로 실전에서 바로 적용 가능한 SEO 최적화 테크닉을 다룹니다.
Next.js SEO의 3가지 핵심 축
SEO is built layer by layer from technical foundation to authority
1. 기술적 SEO (Technical SEO)
- 서버 사이드 렌더링 (SSR/SSG)
- Core Web Vitals 최적화
- 구조화 데이터 (JSON-LD)
- Sitemap/Robots.txt 자동화
2. On-Page SEO
- 메타데이터 최적화
- 시맨틱 HTML 구조
- 내부 링크 전략
- 이미지 최적화
3. 콘텐츠 SEO
- 키워드 전략
- 사용자 의도 반영
- E-E-A-T (Experience, Expertise, Authoritativeness, Trustworthiness)
💡 2025년 SEO 핵심 변화
Google은 이제 사용자 경험(UX)과 콘텐츠 품질을 동등하게 평가합니다. 빠르지만 내용 없는 사이트나, 좋은 콘텐츠지만 느린 사이트 모두 상위 노출이 어렵습니다.
1. 메타데이터 최적화: App Router 방식
Next.js 15의 App Router는 파일 기반 메타데이터 시스템을 제공합니다.
How metadata flows from code to search results
정적 메타데이터 (기본)
typescript
// app/about/page.tsx
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'About POEMA | Digital Agency',
description: 'POEMA는 웹 개발, 이커머스, 퍼포먼스 마케팅을 제공하는 디지털 에이전시입니다.',
keywords: ['웹 개발', '이커머스', '디지털 마케팅', 'POEMA'],
authors: [{ name: 'POEMA Team' }],
openGraph: {
title: 'About POEMA',
description: 'Digital transformation partner for startups',
images: ['/og-image.jpg'],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'About POEMA',
description: 'Digital transformation partner',
images: ['/twitter-image.jpg'],
},
};
export default function AboutPage() {
return <div>...</div>;
}
동적 메타데이터 (추천)
케이스 스터디, 블로그 포스트 등 동적 페이지에 필수입니다.
typescript
// app/portfolio/[slug]/page.tsx
import { Metadata } from 'next';
import { getCaseStudy } from '@/data/marketing/case-studies';
export async function generateMetadata({
params
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params;
const caseStudy = await getCaseStudy(slug);
if (!caseStudy) {
return {
title: 'Portfolio Not Found',
};
}
return {
title: `${caseStudy.title.ko} | POEMA Portfolio`,
description: caseStudy.overview.ko,
keywords: caseStudy.tags.ko,
openGraph: {
title: caseStudy.title.ko,
description: caseStudy.overview.ko,
images: [
{
url: caseStudy.thumbnail,
width: 1200,
height: 630,
alt: caseStudy.title.ko,
},
],
type: 'article',
publishedTime: caseStudy.publishedAt,
},
};
}
✅ 메타데이터 체크리스트
[ ] 모든 페이지에 고유한 title (50-60자 이내)[ ] description 120-160자, 액션 지향적 문구 포함[ ] OG 이미지 1200×630px, 파일 크기 300KB 이하[ ] Twitter Card 설정[ ] 다국어 사이트의 경우 alternates 설정
2. 구조화 데이터: JSON-LD로 리치 스니펫 획득
구조화 데이터는 검색 엔진이 콘텐츠를 이해하도록 돕고, 리치 스니펫을 통해 CTR을 높입니다.
Organization Schema (전역)
typescript
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'POEMA',
url: 'https://poema.agency',
logo: 'https://poema.agency/logo.png',
description: 'Digital agency specializing in web development and e-commerce',
address: {
'@type': 'PostalAddress',
addressCountry: 'KR',
addressLocality: 'Seoul',
},
contactPoint: {
'@type': 'ContactPoint',
telephone: '+82-2-1234-5678',
contactType: 'Customer Service',
email: 'hello@poema.agency',
},
sameAs: [
'https://www.instagram.com/poema.agency',
'https://www.linkedin.com/company/poema',
],
};
return (
<html>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
</head>
<body>{children}</body>
</html>
);
}
Article Schema (블로그 포스트)
typescript
// components/seo/BlogPostJsonLd.tsx
interface BlogPostJsonLdProps {
title: string;
description: string;
publishedAt: string;
author: string;
image: string;
url: string;
}
export function BlogPostJsonLd({ title, description, publishedAt, author, image, url }: BlogPostJsonLdProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: title,
description: description,
image: image,
datePublished: publishedAt,
author: {
'@type': 'Person',
name: author,
},
publisher: {
'@type': 'Organization',
name: 'POEMA',
logo: {
'@type': 'ImageObject',
url: 'https://poema.agency/logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url,
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
FAQPage Schema
typescript
export function FAQJsonLd({ faqs }: { faqs: { question: string; answer: string }[] }) {
const schema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
⚠️ 구조화 데이터 주의사항
Google Rich Results Test로 반드시 검증실제 페이지에 표시된 내용과 일치해야 함과도한 키워드 스터핑 금지업데이트 시 스키마도 함께 수정
3. Sitemap & Robots.txt 자동화
Next.js 15는 파일 기반 sitemap/robots 생성을 지원합니다.
동적 Sitemap 생성
typescript
// app/sitemap.ts
import { MetadataRoute } from 'next';
import { caseStudies } from '@/data/marketing/case-studies';
import { blogPosts } from '@/data/marketing/blog';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://poema.agency';
// Static pages
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 1.0,
},
{
url: `${baseUrl}/services`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${baseUrl}/portfolio`,
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.9,
},
];
// Dynamic case studies
const caseStudyPages: MetadataRoute.Sitemap = caseStudies.map((study) => ({
url: `${baseUrl}/portfolio/${study.slug}`,
lastModified: new Date(study.publishedAt),
changeFrequency: 'monthly' as const,
priority: 0.7,
}));
// Dynamic blog posts
const blogPages: MetadataRoute.Sitemap = blogPosts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.publishedAt),
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
return [...staticPages, ...caseStudyPages, ...blogPages];
}
Robots.txt 설정
typescript
// app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://poema.agency';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/', '/_next/'],
},
{
userAgent: 'Googlebot',
allow: '/',
disallow: ['/api/', '/admin/'],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}
4. Core Web Vitals 최적화
Google uses these metrics as ranking signals
Google은 LCP, FID, CLS를 페이지 경험 지표로 평가합니다.
LCP (Largest Contentful Paint) 최적화
목표: 2.5초 이하
typescript
// 이미지 최적화
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero image"
width={1920}
height={1080}
priority // LCP 이미지에 필수
quality={90}
placeholder="blur"
blurDataURL="data:image/..."
/>
// 폰트 최적화
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // FOUT 방지
preload: true,
});
CLS (Cumulative Layout Shift) 최적화
목표: 0.1 이하
tsx
// 이미지/동영상에 명시적 크기 지정
<Image src="..." width={600} height={400} alt="..." />
// 동적 콘텐츠에 최소 높이 설정
<div className="min-h-[400px]">
{isLoading ? <Skeleton /> : <Content />}
</div>
// 폰트 preload로 FOUT 방지
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
FID/INP 최적화
목표: FID 100ms 이하, INP 200ms 이하
- Dynamic import로 JS 번들 분할
- 무거운 컴포넌트는 below-the-fold에서만 로드
- debounce/throttle로 이벤트 핸들러 최적화
✅ Core Web Vitals 체크리스트
[ ] PageSpeed Insights로 실제 사용자 데이터 확인[ ] Lighthouse CI로 빌드마다 자동 테스트[ ] Vercel Analytics/Google Search Console로 지속 모니터링[ ] 모바일 우선 최적화 (트래픽의 70%+)
5. 다국어 SEO (next-intl 활용)
typescript
// app/[locale]/layout.tsx
import { Metadata } from 'next';
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params;
return {
alternates: {
canonical: `https://poema.agency/${locale}`,
languages: {
'ko-KR': 'https://poema.agency/ko',
'en-US': 'https://poema.agency/en',
},
},
};
}
Hreflang 태그는 Next.js가 자동 생성하지만, Sitemap에는 수동 추가 필요:
typescript
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://poema.agency/ko',
lastModified: new Date(),
alternates: {
languages: {
en: 'https://poema.agency/en',
},
},
},
];
}
6. SEO 체크리스트: 론칭 전 필수 확인
기술적 체크
| 항목 | 도구 | 목표 |
| Core Web Vitals | PageSpeed Insights | LCP <2.5s, FID <100ms, CLS <0.1 |
| 모바일 친화성 | Mobile-Friendly Test | Pass |
| 구조화 데이터 | Rich Results Test | 0 Errors |
| 보안 | SSL Labs | A+ |
| Sitemap | Google Search Console | Indexed |
On-Page 체크
✅ SEO 론칭 체크리스트
[ ] 모든 페이지에 고유한 , [ ] H1 태그 페이지당 1개, 의미 있는 계층 구조[ ] 이미지 alt 텍스트 100% 작성[ ] 내부 링크 전략 (주요 페이지 간 연결)[ ] 404 페이지 커스터마이징[ ] OG 이미지 모든 페이지 설정[ ] Canonical URL 설정[ ] 구조화 데이터 (최소 Organization + Breadcrumb)
실전 팁: 빠른 SEO 성과 내기
1. 롱테일 키워드 공략
"이커머스 개발"보다 "Shopify 한글화 개발 에이전시"가 경쟁률 낮고 전환율 높습니다.
- Google Keyword Planner, Ahrefs로 키워드 리서치
- 블로그 콘텐츠로 롱테일 키워드 대량 타겟팅
- 지역 키워드 활용 ("서울 웹 개발", "강남 쇼핑몰 제작")
2. E-E-A-T 강화
- 팀 소개 페이지에 실명, 사진, 경력 공개
- 케이스 스터디에 클라이언트 로고/추천사
- 블로그에 저자 프로필 명시
- 업계 미디어 기고, 외부 사이트 백링크
3. 기술 블로그로 자연 유입 확보
"Next.js SEO 가이드", "포트폴리오 사이트 제작 방법" 같은 How-to 콘텐츠는 장기적으로 꾸준한 트래픽을 가져옵니다.
- 주 1-2회 발행
- 2,000자 이상 심도 있는 콘텐츠
- 코드 예시, 스크린샷 포함
- 내부 링크로 서비스 페이지 연결
마무리: SEO는 마라톤
SEO 성과는 보통 3-6개월 후 나타납니다. 단기 트래픽이 필요하다면 광고를, 장기 자산을 쌓고 싶다면 SEO를 선택하세요.
Next.js는 SEO를 위한 모든 도구를 제공합니다. 이제 실행만 남았습니다.