treeru.com
SEO

Next.js SEO 100점 만들기 — 메타데이터, sitemap, robots.txt

2026-02-13
Treeru

PageSpeed SEO 점수 100점은 어렵지 않습니다. 몇 가지 설정만 빠짐없이 하면 됩니다. 문제는 놓치기 쉬운 항목이 있다는 겁니다. canonical URL, Open Graph 이미지, JSON-LD 같은 것들.

Next.js App Router 기준으로, SEO에 필요한 모든 설정을 정리했습니다.

100
SEO 점수
동적
메타데이터
자동
sitemap
즉시
IndexNow

1generateMetadata 패턴

Next.js App Router에서 메타데이터는 generateMetadata 함수로 설정합니다. 페이지마다 동적으로 생성할 수 있습니다.

// app/blog/[slug]/layout.tsx
import type { Metadata } from "next";

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = getPost(params.slug);

  return {
    title: post.metaTitle,           // 60자 이내 권장
    description: post.metaDescription, // 155자 이내 권장
    keywords: post.tags,
    authors: [{ name: post.author }],

    // ⭐ canonical URL — 중복 인덱싱 방지
    alternates: {
      canonical: `/blog/${post.slug}`,
    },

    // Open Graph — SNS 공유 시 표시
    openGraph: {
      title: post.metaTitle,
      description: post.metaDescription,
      url: `https://example.com/blog/${post.slug}`,
      type: "article",
      images: [{
        url: "https://example.com/opengraph-image",
        width: 1200, height: 630,
        alt: post.title,
      }],
    },

    // Twitter Card
    twitter: {
      card: "summary_large_image",
      title: post.metaTitle,
      description: post.metaDescription,
    },
  };
}

놓치기 쉬운 항목

  • canonical URL: 없으면 www/non-www, http/https가 별도 페이지로 인식됩니다. 반드시 설정하세요.
  • title 길이: 60자가 넘으면 검색 결과에서 잘립니다. metaTitle을 별도로 관리하는 게 좋습니다.
  • description: 비어 있으면 Google이 자동 추출하는데, 원하는 내용이 아닐 수 있습니다.

2Open Graph 이미지 자동 생성

Next.js는 opengraph-image.tsx 파일을 만들면 OG 이미지를 자동으로 생성합니다. 매번 Figma에서 이미지를 만들 필요가 없습니다.

// app/opengraph-image.tsx
import { ImageResponse } from "next/og";

export const runtime = "edge";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default function Image() {
  return new ImageResponse(
    (
      <div style={{
        display: "flex",
        width: "100%",
        height: "100%",
        background: "linear-gradient(135deg, #1a1a2e, #16213e)",
        color: "white",
        alignItems: "center",
        justifyContent: "center",
        fontSize: 60,
        fontWeight: 700,
      }}>
        Your Site Name
      </div>
    ),
    { ...size }
  );
}

이 파일을 app/ 루트에 놓으면 전체 사이트의 기본 OG 이미지가 됩니다. 블로그 글마다 다른 이미지가 필요하면 app/blog/[slug]/opengraph-image.tsx에서 제목을 동적으로 렌더링할 수도 있습니다.

3sitemap.xml 동적 생성

sitemap.xml은 검색 엔진에게 "이 사이트에 어떤 페이지들이 있는지" 알려주는 파일입니다. Next.js에서 동적으로 생성할 수 있습니다.

// app/sitemap.xml/route.ts
import { getAllPosts } from "@/lib/blog";

export async function GET() {
  const posts = getAllPosts();
  const baseUrl = "https://example.com";

  const staticPages = [
    { url: "/", lastmod: new Date().toISOString(), priority: 1.0 },
    { url: "/blog", lastmod: new Date().toISOString(), priority: 0.8 },
    { url: "/services/web", priority: 0.8 },
    { url: "/support", priority: 0.5 },
  ];

  const blogPages = posts.map(post => ({
    url: `/blog/${post.slug}`,
    lastmod: post.date,
    priority: 0.7,
  }));

  const pages = [...staticPages, ...blogPages];

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map(p => `  <url>
    <loc>${baseUrl}${p.url}</loc>
    ${p.lastmod ? `<lastmod>${p.lastmod}</lastmod>` : ""}
    <priority>${p.priority || 0.5}</priority>
  </url>`).join("\n")}
</urlset>`;

  return new Response(xml, {
    headers: { "Content-Type": "application/xml" },
  });
}

sitemap 주의사항

  • noindex 페이지 제외: 로그인, 관리자 페이지는 sitemap에 넣지 마세요.
  • lastmod 정확히: 실제 수정 시점을 넣어야 합니다. 매번 new Date()를 넣으면 의미가 없습니다.
  • Google Search Console에 제출: sitemap.xml을 만들었으면 Search Console에서 제출해야 크롤러가 인식합니다.

4robots.txt 설정

robots.txt는 크롤러에게 "어디를 크롤링해도 되고, 어디를 하면 안 되는지" 알려줍니다.

// app/robots.txt/route.ts
export function GET() {
  const content = `User-agent: *
Allow: /
Disallow: /admin
Disallow: /api/
Disallow: /auth/

Sitemap: https://example.com/sitemap.xml`;

  return new Response(content, {
    headers: { "Content-Type": "text/plain" },
  });
}
경로설정이유
/Allow공개 페이지는 크롤링 허용
/adminDisallow관리자 페이지 크롤링 차단
/api/DisallowAPI 엔드포인트 인덱싱 방지
/auth/Disallow로그인/회원가입 인덱싱 불필요

5JSON-LD 구조화 데이터

JSON-LD는 검색 엔진에게 페이지의 콘텐츠를 구조화된 형태로 전달합니다. 블로그 글에 BlogPosting 스키마를 넣으면, 검색 결과에 작성일, 작성자, 설명이 리치 결과로 표시될 수 있습니다.

// components/blog/BlogJsonLd.tsx
export function BlogJsonLd({ post }) {
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.title,
    description: post.excerpt,
    datePublished: post.date,
    author: {
      "@type": "Person",
      name: post.author,
    },
    publisher: {
      "@type": "Organization",
      name: "Treeru",
      url: "https://example.com",
    },
    mainEntityOfPage: {
      "@type": "WebPage",
      "@id": `https://example.com/blog/${post.slug}`,
    },
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  );
}

JSON-LD 스키마 종류

페이지 유형스키마리치 결과
블로그 글BlogPosting작성일, 작성자 표시
회사 소개Organization로고, 연락처 패널
FAQFAQPage펼침 FAQ 표시
제품/서비스Product/Service가격, 평점 표시

6IndexNow — 즉시 색인 요청

새 글을 발행하거나 기존 글을 수정하면, 검색 엔진이 크롤링할 때까지 기다려야 합니다. IndexNow를 사용하면 변경 사실을 즉시 알릴 수 있습니다.

// IndexNow API 호출
// Bing, Yandex, Naver(일부)에서 지원

const response = await fetch(
  "https://api.indexnow.org/indexnow", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      host: "example.com",
      key: "your-indexnow-key",
      urlList: [
        "https://example.com/blog/new-post",
        "https://example.com/blog/updated-post",
      ],
    }),
  }
);

// 200 OK → 제출 성공
// 키 파일을 example.com/your-key.txt에 배치 필요

IndexNow vs Google Ping

Google은 IndexNow를 지원하지 않습니다. 대신 Google Search Console의 URL 검사 도구에서 수동으로 색인 요청하거나, sitemap을 다시 제출하면 됩니다. 하지만 Bing은 IndexNow로 수 분 내에 인덱싱되므로 활용 가치가 있습니다.

SEO 100점 체크리스트

모든 페이지에 title, description, canonical URL 설정
Open Graph / Twitter Card 메타 태그 추가
opengraph-image.tsx로 OG 이미지 자동 생성
sitemap.xml에 모든 공개 페이지 포함, lastmod 정확히 기입
robots.txt로 admin, api, auth 크롤링 차단
JSON-LD 구조화 데이터 추가 (BlogPosting, Organization 등)
IndexNow로 새 글 발행 시 검색 엔진에 즉시 알림
Google Search Console에 사이트 등록 및 sitemap 제출

SEO 점수와 실제 검색 순위는 다릅니다. SEO 100점은 기술적 설정이 완벽하다는 의미이며, 검색 순위는 콘텐츠 품질, 백링크, 사용자 경험 등 다른 요소에 의해 결정됩니다.

T

Treeru

웹 개발, IT 인프라, AI 솔루션 분야의 실무 인사이트를 공유합니다. 기업의 디지털 전환을 돕는 IT 파트너, Treeru입니다.

공유

댓글

(4개)
4.50/ 5

로그인하면 댓글을 작성할 수 있습니다.

2026-02-25
555.0

IndexNow로 발행 즉시 Bing에 알려주는 거, 실제로 해보니 몇 분 안에 인덱싱 되더라고요. 네이버도 IndexNow 지원하면 좋겠는데.

2026-02-21
4.554.5

opengraph-image.tsx로 OG 이미지를 자동 생성하는 방법이 깔끔합니다. 매번 디자이너한테 요청했는데 이제 코드로 만들 수 있겠네요.

2026-02-18
454.0

JSON-LD를 BlogPosting으로 넣으니 Google 검색 결과에 날짜랑 작성자가 표시되기 시작했습니다. 클릭률이 올라간 것 같아요.

관련 글

© 2026 TreeRU. All rights reserved.

본 콘텐츠의 저작권은 TreeRU에 있으며, 출처를 밝히지 않은 무단 전재 및 재배포를 금합니다. 인용 시 출처(treeru.com)를 반드시 명시해 주세요.