| name | seo-guide |
| description | SaaS SEO 최적화 가이드 - 검색엔진 상위 노출을 위한 완벽 가이드 |
| triggers | SEO, 검색, 메타태그, sitemap, 구글 |
SaaS SEO 최적화 가이드
SaaS 프로젝트의 검색엔진 최적화(SEO)를 위한 완벽 가이드
1. 메타태그 설정
Next.js App Router (권장)
// src/app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
// 기본 메타데이터
title: {
default: 'MySaaS - 서비스 설명',
template: '%s | MySaaS',
},
description: '서비스에 대한 150-160자 설명. 핵심 키워드 포함.',
keywords: ['SaaS', '키워드1', '키워드2', '키워드3'],
authors: [{ name: 'Your Name' }],
creator: 'Your Company',
publisher: 'Your Company',
// 로봇 설정
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
// Open Graph (소셜 공유)
openGraph: {
type: 'website',
locale: 'ko_KR',
url: 'https://mysaas.com',
siteName: 'MySaaS',
title: 'MySaaS - 서비스 설명',
description: '서비스에 대한 설명',
images: [
{
url: 'https://mysaas.com/og-image.png',
width: 1200,
height: 630,
alt: 'MySaaS',
},
],
},
// Twitter Card
twitter: {
card: 'summary_large_image',
title: 'MySaaS - 서비스 설명',
description: '서비스에 대한 설명',
images: ['https://mysaas.com/twitter-image.png'],
creator: '@yourhandle',
},
// 아이콘
icons: {
icon: '/favicon.ico',
shortcut: '/favicon-16x16.png',
apple: '/apple-touch-icon.png',
},
// 기타
manifest: '/manifest.json',
alternates: {
canonical: 'https://mysaas.com',
languages: {
'ko-KR': 'https://mysaas.com/ko',
'en-US': 'https://mysaas.com/en',
},
},
// 검증
verification: {
google: 'google-site-verification-code',
naver: 'naver-site-verification-code',
},
};
동적 페이지 메타데이터
// src/app/blog/[slug]/page.tsx
import { Metadata } from 'next';
type Props = {
params: { slug: string };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author.name],
},
};
}
2. Sitemap 생성
정적 Sitemap
// src/app/sitemap.ts
import { MetadataRoute } from 'next';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://mysaas.com';
// 정적 페이지
const staticPages = [
'',
'/features',
'/pricing',
'/about',
'/contact',
'/blog',
].map((route) => ({
url: `${baseUrl}${route}`,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
priority: route === '' ? 1 : 0.8,
}));
return staticPages;
}
동적 Sitemap (블로그 포함)
// src/app/sitemap.ts
import { MetadataRoute } from 'next';
import { getAllPosts } from '@/lib/blog';
import { getAllProducts } from '@/lib/products';
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://mysaas.com';
// 정적 페이지
const staticPages = [
{ url: baseUrl, priority: 1 },
{ url: `${baseUrl}/features`, priority: 0.9 },
{ url: `${baseUrl}/pricing`, priority: 0.9 },
{ url: `${baseUrl}/blog`, priority: 0.8 },
].map((page) => ({
...page,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
}));
// 블로그 포스트
const posts = await getAllPosts();
const blogPages = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
// 제품 페이지
const products = await getAllProducts();
const productPages = products.map((product) => ({
url: `${baseUrl}/products/${product.slug}`,
lastModified: new Date(product.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}));
return [...staticPages, ...blogPages, ...productPages];
}
3. Robots.txt
// src/app/robots.ts
import { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://mysaas.com';
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/api/',
'/admin/',
'/dashboard/',
'/private/',
'/*.json$',
],
},
{
userAgent: 'Googlebot',
allow: '/',
},
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}
4. 구조화된 데이터 (JSON-LD)
조직 정보
// src/components/JsonLd.tsx
export function OrganizationJsonLd() {
const schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'MySaaS',
url: 'https://mysaas.com',
logo: 'https://mysaas.com/logo.png',
sameAs: [
'https://twitter.com/mysaas',
'https://linkedin.com/company/mysaas',
'https://github.com/mysaas',
],
contactPoint: {
'@type': 'ContactPoint',
telephone: '+82-2-1234-5678',
contactType: 'customer service',
availableLanguage: ['Korean', 'English'],
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
SaaS 제품
// src/components/ProductJsonLd.tsx
export function SoftwareApplicationJsonLd() {
const schema = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'MySaaS',
operatingSystem: 'Web',
applicationCategory: 'BusinessApplication',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'KRW',
priceValidUntil: '2026-12-31',
},
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '4.8',
ratingCount: '150',
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
블로그 포스트
// src/components/ArticleJsonLd.tsx
type Props = {
title: string;
description: string;
publishedAt: string;
author: string;
image: string;
url: string;
};
export function ArticleJsonLd({ title, description, publishedAt, author, image, url }: Props) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: title,
description: description,
image: image,
datePublished: publishedAt,
author: {
'@type': 'Person',
name: author,
},
publisher: {
'@type': 'Organization',
name: 'MySaaS',
logo: {
'@type': 'ImageObject',
url: 'https://mysaas.com/logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': url,
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
FAQ
// src/components/FaqJsonLd.tsx
type FaqItem = {
question: string;
answer: string;
};
export function FaqJsonLd({ faqs }: { faqs: FaqItem[] }) {
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) }}
/>
);
}
5. 이미지 최적화
Next.js Image 컴포넌트
// src/components/OptimizedImage.tsx
import Image from 'next/image';
type Props = {
src: string;
alt: string;
width: number;
height: number;
priority?: boolean;
};
export function OptimizedImage({ src, alt, width, height, priority = false }: Props) {
return (
<Image
src={src}
alt={alt} // SEO: 반드시 의미있는 alt 텍스트
width={width}
height={height}
priority={priority} // LCP 이미지는 priority 설정
loading={priority ? 'eager' : 'lazy'}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
);
}
next.config.js 이미지 설정
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{
protocol: 'https',
hostname: 'images.mysaas.com',
},
],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
6. 성능 최적화 (Core Web Vitals)
LCP (Largest Contentful Paint)
// 중요 이미지에 priority 추가
<Image src="/hero.jpg" alt="Hero" priority />
// 폰트 최적화
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // 폰트 로딩 중 텍스트 표시
});
CLS (Cumulative Layout Shift)
// 이미지 크기 명시
<Image src="/image.jpg" width={800} height={600} alt="..." />
// 스켈레톤 로더
function Skeleton({ className }: { className?: string }) {
return (
<div className={`animate-pulse bg-gray-200 ${className}`} />
);
}
// 광고/동적 콘텐츠 공간 예약
<div style={{ minHeight: '250px' }}>
<AdComponent />
</div>
INP (Interaction to Next Paint)
// React.memo로 불필요한 리렌더링 방지
const ExpensiveComponent = React.memo(function ExpensiveComponent() {
// ...
});
// useTransition으로 비동기 상태 업데이트
const [isPending, startTransition] = useTransition();
function handleClick() {
startTransition(() => {
setItems(computeExpensiveList());
});
}
7. 국제화 SEO (다국어)
hreflang 설정
// src/app/layout.tsx
export const metadata: Metadata = {
alternates: {
canonical: 'https://mysaas.com',
languages: {
'ko-KR': 'https://mysaas.com/ko',
'en-US': 'https://mysaas.com/en',
'ja-JP': 'https://mysaas.com/ja',
'x-default': 'https://mysaas.com',
},
},
};
언어별 Sitemap
// src/app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const languages = ['ko', 'en', 'ja'];
const baseUrl = 'https://mysaas.com';
const pages = ['', '/features', '/pricing'];
return pages.flatMap((page) =>
languages.map((lang) => ({
url: `${baseUrl}/${lang}${page}`,
lastModified: new Date(),
changeFrequency: 'weekly' as const,
priority: page === '' ? 1 : 0.8,
}))
);
}
8. 검색엔진 등록
Google Search Console
- https://search.google.com/search-console 접속
- 속성 추가 → URL 접두어 또는 도메인
- 소유권 확인 (HTML 태그, DNS, 파일 업로드 중 선택)
- sitemap.xml 제출
Naver Search Advisor
- https://searchadvisor.naver.com 접속
- 사이트 등록
- 소유확인 (HTML 태그)
- 사이트맵 제출
Bing Webmaster Tools
- https://www.bing.com/webmasters 접속
- 사이트 추가
- 소유권 확인
- 사이트맵 제출
9. SEO 체크리스트
┌─────────────────────────────────────────────────────────────┐
│ ✅ SEO 체크리스트 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 📝 메타 태그 │
│ □ title 태그 (50-60자) │
│ □ description 메타 태그 (150-160자) │
│ □ Open Graph 태그 │
│ □ Twitter Card 태그 │
│ □ canonical URL │
│ │
│ 🗺️ 사이트맵 & 로봇 │
│ □ sitemap.xml 생성 │
│ □ robots.txt 설정 │
│ □ Google Search Console 등록 │
│ □ Naver Search Advisor 등록 │
│ │
│ 📊 구조화된 데이터 │
│ □ Organization JSON-LD │
│ □ Product/Service JSON-LD │
│ □ Article JSON-LD (블로그) │
│ □ FAQ JSON-LD │
│ □ BreadcrumbList JSON-LD │
│ │
│ 🖼️ 이미지 최적화 │
│ □ alt 텍스트 │
│ □ WebP/AVIF 포맷 │
│ □ 적절한 크기 │
│ □ lazy loading │
│ │
│ ⚡ 성능 (Core Web Vitals) │
│ □ LCP < 2.5초 │
│ □ INP < 200ms │
│ □ CLS < 0.1 │
│ │
│ 🌐 기타 │
│ □ HTTPS 사용 │
│ □ 모바일 친화적 │
│ □ 빠른 로딩 속도 │
│ □ 명확한 URL 구조 │
│ □ 내부 링크 최적화 │
│ │
└─────────────────────────────────────────────────────────────┘
10. SEO 모니터링 도구
무료 도구
- Google Search Console: 검색 성능, 인덱싱 상태
- Google PageSpeed Insights: Core Web Vitals 측정
- Lighthouse: 종합 SEO 점수
- Screaming Frog SEO Spider: 사이트 크롤링 (500 URL 무료)
유료 도구
- Ahrefs: 백링크, 키워드 분석
- SEMrush: 종합 SEO 도구
- Moz Pro: 도메인 권위, 키워드 추적
코드로 SEO 점수 확인
# Lighthouse CLI
npm install -g lighthouse
lighthouse https://mysaas.com --output html --output-path ./report.html
# PageSpeed Insights API
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://mysaas.com&strategy=mobile"
11. 파비콘 자동 생성
방법 1: AI 이미지 생성 + 자동 변환
// scripts/generate-favicon.ts
import sharp from 'sharp';
import { toIco } from 'ico-endec';
import fs from 'fs/promises';
import path from 'path';
interface FaviconConfig {
inputImage: string; // AI 생성 이미지 또는 로고
outputDir: string;
siteName: string;
themeColor: string;
}
async function generateFavicons(config: FaviconConfig) {
const { inputImage, outputDir, siteName, themeColor } = config;
// 필요한 사이즈 정의
const sizes = [
{ name: 'favicon-16x16.png', size: 16 },
{ name: 'favicon-32x32.png', size: 32 },
{ name: 'favicon-48x48.png', size: 48 },
{ name: 'apple-touch-icon.png', size: 180 },
{ name: 'android-chrome-192x192.png', size: 192 },
{ name: 'android-chrome-512x512.png', size: 512 },
{ name: 'mstile-150x150.png', size: 150 },
];
// 디렉토리 생성
await fs.mkdir(outputDir, { recursive: true });
// 각 사이즈로 변환
for (const { name, size } of sizes) {
await sharp(inputImage)
.resize(size, size, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } })
.png()
.toFile(path.join(outputDir, name));
console.log(`✅ Generated: ${name}`);
}
// favicon.ico 생성 (16x16, 32x32, 48x48 포함)
const ico16 = await sharp(inputImage).resize(16, 16).png().toBuffer();
const ico32 = await sharp(inputImage).resize(32, 32).png().toBuffer();
const ico48 = await sharp(inputImage).resize(48, 48).png().toBuffer();
const icoBuffer = toIco([ico16, ico32, ico48]);
await fs.writeFile(path.join(outputDir, 'favicon.ico'), icoBuffer);
console.log('✅ Generated: favicon.ico');
// manifest.json 생성
const manifest = {
name: siteName,
short_name: siteName,
icons: [
{ src: '/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' },
{ src: '/android-chrome-512x512.png', sizes: '512x512', type: 'image/png' },
],
theme_color: themeColor,
background_color: '#ffffff',
display: 'standalone',
};
await fs.writeFile(
path.join(outputDir, 'manifest.json'),
JSON.stringify(manifest, null, 2)
);
console.log('✅ Generated: manifest.json');
// browserconfig.xml 생성 (Microsoft)
const browserconfig = `<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>${themeColor}</TileColor>
</tile>
</msapplication>
</browserconfig>`;
await fs.writeFile(path.join(outputDir, 'browserconfig.xml'), browserconfig);
console.log('✅ Generated: browserconfig.xml');
console.log('\n🎉 All favicons generated successfully!');
}
// 실행
generateFavicons({
inputImage: './logo.png', // AI로 생성한 로고 또는 기존 로고
outputDir: './public',
siteName: 'MySaaS',
themeColor: '#3B82F6',
});
방법 2: AI 로고 생성 스크립트
// scripts/generate-logo-and-favicon.ts
import OpenAI from 'openai';
import sharp from 'sharp';
import fs from 'fs/promises';
const openai = new OpenAI();
interface LogoConfig {
serviceName: string;
style: 'minimal' | 'modern' | 'playful' | 'corporate';
primaryColor: string;
description: string;
}
async function generateLogoWithAI(config: LogoConfig): Promise<Buffer> {
const prompt = `
Create a simple, modern logo icon for "${config.serviceName}".
Style: ${config.style}
Primary color: ${config.primaryColor}
Description: ${config.description}
Requirements:
- Square format, centered
- Works well at small sizes (16x16 to 512x512)
- Clean, minimal design
- No text, icon only
- Flat design, no gradients
- White or transparent background
`;
const response = await openai.images.generate({
model: 'dall-e-3',
prompt,
n: 1,
size: '1024x1024',
quality: 'hd',
response_format: 'b64_json',
});
const imageData = response.data[0].b64_json!;
return Buffer.from(imageData, 'base64');
}
async function main() {
console.log('🎨 Generating logo with AI...');
const logoBuffer = await generateLogoWithAI({
serviceName: 'MySaaS',
style: 'modern',
primaryColor: '#3B82F6',
description: 'A productivity SaaS for teams',
});
// 원본 로고 저장
await fs.writeFile('./logo-original.png', logoBuffer);
console.log('✅ Saved: logo-original.png');
// 배경 제거 및 정사각형으로 크롭
await sharp(logoBuffer)
.resize(1024, 1024, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } })
.png()
.toFile('./logo.png');
console.log('✅ Saved: logo.png');
console.log('\n🚀 Now run: npx ts-node scripts/generate-favicon.ts');
}
main().catch(console.error);
방법 3: 온라인 도구 사용
# RealFaviconGenerator CLI
npm install -g real-favicon
# favicon 생성 (logo.png 필요)
real-favicon generate ./faviconDescription.json ./faviconData.json ./public
// faviconDescription.json
{
"masterPicture": "./logo.png",
"iconsPath": "/",
"design": {
"ios": {
"pictureAspect": "backgroundAndMargin",
"backgroundColor": "#ffffff",
"margin": "14%"
},
"desktopBrowser": {},
"windows": {
"pictureAspect": "whiteSilhouette",
"backgroundColor": "#3B82F6"
},
"androidChrome": {
"pictureAspect": "shadow",
"themeColor": "#3B82F6",
"manifest": {
"name": "MySaaS",
"display": "standalone"
}
}
},
"settings": {
"compression": 2,
"scalingAlgorithm": "Mitchell"
}
}
Next.js에서 파비콘 적용
// src/app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
icons: {
icon: [
{ url: '/favicon.ico', sizes: 'any' },
{ url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
apple: [
{ url: '/apple-touch-icon.png', sizes: '180x180', type: 'image/png' },
],
other: [
{ rel: 'mask-icon', url: '/safari-pinned-tab.svg', color: '#3B82F6' },
],
},
manifest: '/manifest.json',
themeColor: '#3B82F6',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'MySaaS',
},
};
필요한 패키지 설치
# 파비콘 생성 도구
npm install --save-dev sharp ico-endec
# AI 로고 생성 (선택)
npm install openai
# 타입
npm install --save-dev @types/sharp
12. SEO 개발 워크플로우
Step 1: 초기 설정
# 1. SEO 관련 패키지 설치
npm install next-seo next-sitemap
# 2. 파비콘 생성 도구 설치
npm install --save-dev sharp ico-endec
# 3. 로고/파비콘 생성
npx ts-node scripts/generate-logo-and-favicon.ts
npx ts-node scripts/generate-favicon.ts
Step 2: 메타데이터 설정
// src/app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://mysaas.com'),
title: {
default: 'MySaaS - 서비스 한 줄 설명',
template: '%s | MySaaS',
},
description: '서비스에 대한 150-160자 설명',
// ... 나머지 설정
};
Step 3: 사이트맵 & robots.txt
# next-sitemap 설정 파일 생성
touch next-sitemap.config.js
// next-sitemap.config.js
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://mysaas.com',
generateRobotsTxt: true,
sitemapSize: 7000,
changefreq: 'weekly',
priority: 0.7,
exclude: ['/api/*', '/admin/*', '/dashboard/*'],
robotsTxtOptions: {
additionalSitemaps: [
'https://mysaas.com/sitemap-blog.xml',
],
policies: [
{ userAgent: '*', allow: '/', disallow: ['/api/', '/admin/'] },
],
},
};
// package.json
{
"scripts": {
"postbuild": "next-sitemap"
}
}
Step 4: 구조화된 데이터 추가
// src/app/layout.tsx
import { OrganizationJsonLd, SoftwareApplicationJsonLd } from '@/components/JsonLd';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<head>
<OrganizationJsonLd />
<SoftwareApplicationJsonLd />
</head>
<body>{children}</body>
</html>
);
}
Step 5: OG 이미지 자동 생성
// src/app/api/og/route.tsx
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'MySaaS';
const description = searchParams.get('description') || '';
return new ImageResponse(
(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#1a1a2e',
color: 'white',
padding: '40px',
}}
>
<div style={{ fontSize: 60, fontWeight: 'bold', marginBottom: 20 }}>
{title}
</div>
<div style={{ fontSize: 30, opacity: 0.8, textAlign: 'center' }}>
{description}
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}
// 사용 예시 - 동적 메타데이터
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
openGraph: {
images: [`/api/og?title=${encodeURIComponent(post.title)}&description=${encodeURIComponent(post.excerpt)}`],
},
};
}
Step 6: 검색엔진 등록
┌─────────────────────────────────────────────────────────────┐
│ 🔍 검색엔진 등록 순서 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 배포 완료 후 │
│ │
│ 2. Google Search Console │
│ → https://search.google.com/search-console │
│ → 속성 추가 → URL 접두어 │
│ → HTML 태그로 소유권 확인 │
│ → sitemap.xml 제출 │
│ │
│ 3. Naver Search Advisor │
│ → https://searchadvisor.naver.com │
│ → 사이트 추가 │
│ → HTML 태그로 소유확인 │
│ → 사이트맵 제출 │
│ │
│ 4. Bing Webmaster Tools │
│ → https://www.bing.com/webmasters │
│ → Google Search Console에서 가져오기 (간편) │
│ │
│ 5. 인덱싱 요청 │
│ → Google: URL 검사 → 색인 생성 요청 │
│ → Naver: 웹페이지 수집 요청 │
│ │
└─────────────────────────────────────────────────────────────┘
Step 7: SEO 점수 확인
# Lighthouse로 SEO 점수 측정
npx lighthouse https://mysaas.com --only-categories=seo --output=html --output-path=./seo-report.html
# 또는 Chrome DevTools에서
# F12 → Lighthouse 탭 → SEO 체크 → Analyze
개발 체크리스트
┌─────────────────────────────────────────────────────────────┐
│ 📋 SEO 개발 체크리스트 │
├─────────────────────────────────────────────────────────────┤
│ │
│ □ 1. 패키지 설치 (next-seo, next-sitemap, sharp) │
│ □ 2. 로고 생성 (AI 또는 직접 디자인) │
│ □ 3. 파비콘 자동 생성 스크립트 실행 │
│ □ 4. layout.tsx 메타데이터 설정 │
│ □ 5. next-sitemap.config.js 설정 │
│ □ 6. JSON-LD 컴포넌트 추가 │
│ □ 7. OG 이미지 API 라우트 생성 │
│ □ 8. 빌드 및 배포 │
│ □ 9. Google Search Console 등록 │
│ □ 10. Naver Search Advisor 등록 │
│ □ 11. Lighthouse SEO 점수 확인 (목표: 90+) │
│ □ 12. Core Web Vitals 확인 │
│ │
└─────────────────────────────────────────────────────────────┘
13. 빠른 적용 템플릿
# SEO 패키지 설치
npm install next-seo next-sitemap
# next-sitemap 설정
npx next-sitemap
next-seo 사용
// src/app/layout.tsx
import { DefaultSeo } from 'next-seo';
import SEO from '../next-seo.config';
export default function RootLayout({ children }) {
return (
<html>
<head>
<DefaultSeo {...SEO} />
</head>
<body>{children}</body>
</html>
);
}
// next-seo.config.ts
export default {
titleTemplate: '%s | MySaaS',
defaultTitle: 'MySaaS - 서비스 설명',
description: '서비스 설명 150-160자',
openGraph: {
type: 'website',
locale: 'ko_KR',
url: 'https://mysaas.com',
siteName: 'MySaaS',
},
twitter: {
handle: '@mysaas',
cardType: 'summary_large_image',
},
};