About→

Study

← Studies

POKIT 웹사이트 SEO 개선 — 현재 구조부터 정리하기

POKIT 웹사이트를 운영하면서 앞으로 검색을 통한 자연 유입을 어떻게 늘릴 수 있을지 지속적으로 분석해보려고 한다.

단순히 SEO를 한 번 적용하고 끝내는 것이 아니라, 실제로 어떤 검색어에서 노출되는지, 어떤 콘텐츠가 유입을 만들어내는지, 검색엔진에서 어떤 변화가 나타나는지를 매일 확인하면서 조금씩 개선해볼 생각이다.

그런데 그 전에 한 가지를 먼저 정리할 필요가 있었다.

지금 우리가 만든 웹사이트는 정확히 어떤 방식으로 SEO가 되어 있는가?

처음부터 SEO를 하나의 계획으로 설계했다기보다는 서비스를 만들면서 metadata, sitemap, Open Graph, JSON-LD, 다국어 URL 등의 기능을 하나씩 적용해왔다.

그래서 앞으로 SEO 성과를 분석하기 전에 먼저 현재 구조를 정리해보기로 했다.

이번 글에서는 POKIT 웹사이트가 현재 어떤 방식으로 검색엔진에 콘텐츠를 전달하고 있는지, 그리고 그 과정에서 Next.js가 어떤 역할을 하고 있는지를 정리한다.

그리고 이 글을 시작점으로 앞으로는 실제 검색 노출과 자연 유입 데이터를 확인하면서,

“SEO를 적용했다”에서 끝나는 것이 아니라 “어떻게 하면 실제 자연 유입을 만들 수 있을까?”

를 계속 분석해보려고 한다.

1. 페이지마다 고유한 메타 정보 만들기

generateMetadata / Metadata API

가장 먼저 적용한 것은 페이지별 메타 정보다.

웹페이지에는 <title>, <meta name="description"> 같은 정보가 존재한다.

예를 들어 모든 페이지가 다음과 같다면,

html
<title>POKIT</title>
<meta name="description" content="POKIT 웰니스 루틴">

검색엔진 입장에서는 각각의 페이지가 어떤 내용을 가지고 있는지 구분하기 어렵다.

반대로 글마다 고유한 제목과 설명을 제공하면 검색결과에서도 콘텐츠의 주제를 명확하게 전달할 수 있다.

Next.js에서는 Metadata API를 이용할 수 있다.

text
src/app/layout.tsx
        ↓
사이트 전체 기본 metadata

articles/[slug]/page.tsx
        ↓
generateMetadata()
        ↓
Sanity에서 제목·설명 조회
        ↓
페이지별 metadata 생성

POKIT에서는 Article의 제목과 설명을 CMS에서 가져와 글마다 다른 metadata가 생성되도록 구성했다.

Next.js가 아니라면?

결국 해야 하는 일은 동일하다.

html
<head>
  <title>수면 루틴 만드는 방법</title>
  <meta
    name="description"
    content="규칙적인 수면 루틴을 만드는 방법을 알아봅니다."
  />
</head>

Next.js는 이런 metadata를 프레임워크의 방식으로 관리할 수 있도록 API를 제공하는 것이다.

2. Sitemap으로 콘텐츠를 발견할 수 있게 만들기

src/app/sitemap.ts

검색엔진이 사이트의 모든 페이지를 발견하려면 URL 목록을 제공하는 것이 좋다.

POKIT에서는 정적 페이지뿐만 아니라 Sanity에 저장된 Article URL도 sitemap에 포함되도록 구성했다.

text
정적 페이지
+
Sanity Article slug
        ↓
Article URL 생성
        ↓
sitemap.xml

Next.js에서는 app/sitemap.ts를 만들고 MetadataRoute.Sitemap을 반환하면 된다.

결과적으로 다음과 같은 sitemap을 제공한다.

text
https://www.pokitstory.com/sitemap.xml

sitemap은 검색 순위를 직접 올려주는 기능이라기보다는,

“내 사이트에는 이런 URL들이 있습니다.”

라고 검색엔진에 알려주는 역할에 가깝다.

특히 콘텐츠가 계속 추가되는 사이트에서는 새로운 글을 검색엔진이 발견하는 데 도움이 된다.

3. robots.txt로 크롤링 규칙 정하기

src/app/robots.ts

sitemap이 검색엔진에게 무엇이 존재하는지 알려주는 역할이라면,

robots.txt는 어떤 경로를 크롤링해도 되는지에 대한 규칙을 제공한다.

POKIT에서는 CMS 관리 화면인 /studio/ 같은 경로가 검색엔진에 노출되지 않도록 제한했다.

text
/
├── /articles/      → 허용
├── /studio/        → 차단
└── sitemap.xml     → 위치 안내

Next.js에서는 app/robots.ts를 통해 이를 관리할 수 있다.

다만 robots.txt를 SEO를 위한 "점수 상승 기능"으로 생각하기보다는,

검색엔진에 불필요한 경로가 노출되지 않도록 하는 가드레일

이라고 보는 것이 더 정확하다.

4. 사이트 전체의 URL과 제목 규칙 정리하기

metadataBase / title.template

사이트 전체에서 반복적으로 사용되는 metadata 규칙도 정리했다.

metadataBase

OG 이미지와 같은 상대 URL을 사용할 때 기준이 되는 URL이다.

text
https://www.pokitstory.com

이를 기준으로 상대 경로를 절대 URL로 변환할 수 있다.

title.template

페이지 제목도 일정한 규칙을 적용할 수 있다.

예를 들어,

text
%s | POKIT

이라는 템플릿을 사용하면,

text
수면 루틴 | POKIT
아침 루틴 | POKIT
습관 만들기 | POKIT

처럼 일관된 제목을 만들 수 있다.

이런 설정은 SEO뿐만 아니라 사이트 전체의 브랜딩과 일관성을 유지하는 데도 도움이 된다.

5. Canonical과 hreflang으로 다국어 URL 정리하기

POKIT은 다국어 페이지를 사용하기 때문에 URL 간의 관계를 명확하게 정의할 필요가 있었다.

Canonical

canonical은 여러 URL 중

“이 콘텐츠의 대표 URL은 이것입니다.”

라고 알려주는 역할을 한다.

예를 들어 동일한 콘텐츠가 여러 URL로 접근될 수 있다면 대표 URL을 지정할 수 있다.

html
<link
  rel="canonical"
  href="https://www.pokitstory.com/ko/articles/sleep-routine"
/>

hreflang

다국어 페이지에서는 언어별 대응 관계도 알려줄 수 있다.

text
ko → 한국어 페이지
en → 영어 페이지
ja → 일본어 페이지
x-default → 기본 페이지

Next.js에서는 alternates를 이용해 이를 관리할 수 있다.

POKIT에서는 localeAlternates()라는 헬퍼를 만들어 canonical과 언어별 URL을 일관된 규칙으로 생성하도록 했다.

특히 아직 영어로 번역되지 않은 글은 영어 URL을 만들어놓고 한국어 콘텐츠를 보여주는 방식으로 처리하지 않았다.

이 부분은 뒤에서 설명할 noindex와 함께 관리했다.

6. 검색엔진이 읽을 수 있는 HTML에 콘텐츠 넣기

App Router + RSC + generateStaticParams

이번 작업에서 가장 중요하게 생각한 부분이다.

처음에는 홈에서 Article을 모달로 열어 콘텐츠를 보여주는 구조였다.

사용자 입장에서는 문제가 없었다.

text
홈
 ↓
Article 선택
 ↓
모달에서 콘텐츠 확인

하지만 SEO 관점에서는 문제가 생긴다.

URL이 다음처럼 되어 있었기 때문이다.

text
/?article=sleep-routine

실제로는 다른 글을 보고 있는데 검색엔진 입장에서는 기본적으로 홈 페이지와 같은 URL 구조다.

이를 다음과 같이 변경했다.

text
/ko/articles/sleep-routine
/ko/articles/morning-routine
/ko/articles/weight-management

그리고 Article 페이지에서 서버가 콘텐츠를 렌더링하도록 구성했다.

text
사용자가 Article URL 접근
        ↓
Next.js 서버
        ↓
Sanity에서 Article 조회
        ↓
서버에서 HTML 생성
        ↓
브라우저에 HTML 전달

즉, 클라이언트에서 JavaScript를 실행한 뒤에야 본문이 나타나는 구조가 아니라,

첫 HTML 응답에 콘텐츠가 포함될 수 있는 구조

로 만든 것이다.

또한 generateStaticParams를 사용하면 미리 알고 있는 Article URL을 정적으로 생성할 수도 있다.

여기서 중요한 것은 Next.js 자체가 아니다.

핵심은 다음과 같다.

검색엔진이 접근할 수 있는 고유 URL과, 그 URL의 HTML에 실제 콘텐츠가 존재해야 한다.

Next.js에서는 RSC, SSR, SSG 등을 통해 이를 편하게 구현할 수 있다.

7. 기존 URL을 새로운 URL로 연결하기

SEO 구조를 개선하면서 기존 링크를 그대로 버릴 수도 없었다.

예전에는 다음과 같은 URL이 사용되고 있었다.

text
/?article=sleep-routine

새로운 구조는 다음과 같다.

text
/ko/articles/sleep-routine

따라서 기존 URL에 접근하면 새로운 URL로 영구 리다이렉트하도록 만들었다.

text
/?article=sleep-routine
        ↓
308 Redirect
        ↓
/ko/articles/sleep-routine

Next.js에서는 middleware에서 이를 처리할 수 있다.

여기서 중요한 것은 영구 리다이렉트라는 점이다.

기존 URL이 이미 공유되거나 외부에서 링크되고 있었다면 새로운 URL로 연결해 기존 URL에서 발생하던 신호가 새 URL로 이어질 수 있도록 한다.

8. Open Graph와 Twitter Card

검색엔진뿐만 아니라 공유할 때의 모습도 중요하다.

카카오톡, X, Slack 등의 서비스에서 URL을 공유하면 페이지의 제목과 이미지가 카드 형태로 표시된다.

이를 위해 Article마다 다음과 같은 정보를 제공했다.

text
og:title
og:description
og:image

twitter:card
twitter:title
twitter:description
twitter:image

Next.js에서는 Metadata API의

text
openGraph
twitter

속성으로 관리할 수 있다.

이것은 직접적인 검색 순위 요소라기보다는,

콘텐츠를 공유했을 때 클릭하고 싶은 형태로 보여주는 장치

에 가깝다.

특히 콘텐츠 마케팅을 목적으로 운영하는 사이트라면 실제 유입에 영향을 줄 수 있는 중요한 요소다.

9. Article과 Breadcrumb을 JSON-LD로 제공하기

검색엔진이 페이지의 내용을 더 구조적으로 이해할 수 있도록 JSON-LD도 추가했다.

Article 페이지에는 다음과 같은 정보를 제공할 수 있다.

text
Article
├── headline
├── description
├── image
└── datePublished

Breadcrumb은 페이지의 계층 구조를 표현한다.

text
홈
 ↓
Article
 ↓
수면 루틴

이를 application/ld+json 형태로 삽입한다.

html
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  ...
}
</script>

POKIT에서는 이를 JsonLd 컴포넌트로 관리했다.

이 역시 SEO 순위를 직접 보장하는 기능은 아니다.

다만 검색엔진이

“이 페이지는 Article이고, 사이트에서 이런 위치에 있는 콘텐츠다.”

라고 이해할 수 있도록 구조화된 정보를 제공하는 것이다.

10. 번역되지 않은 페이지는 noindex

다국어 사이트에서는 단순히 언어별 URL을 만드는 것만으로 끝나지 않는다.

예를 들어,

text
/en/articles/sleep-routine

이라는 영어 URL이 존재하는데 실제 본문은 한국어라면 문제가 될 수 있다.

사용자는 영어 페이지라고 생각하고 들어왔는데 실제 콘텐츠는 한국어이기 때문이다.

그래서 아직 번역되지 않은 언어 페이지는 다음과 같이 처리했다.

text
/en/articles/sleep-routine
        ↓
noindex, follow

그리고 sitemap에서도 제외했다.

즉,

실제로 존재하지 않는 번역 페이지를 검색엔진에 색인시키지 않는다.

라는 원칙이다.

11. 홈의 모달 UX와 SEO용 상세 페이지를 분리하기

이번 SEO 작업에서 가장 중요한 제품적인 결정이었다.

모달 자체가 나쁜 것은 아니다.

POKIT의 홈에서는 사용자가 여러 콘텐츠를 빠르게 탐색할 수 있도록 모달 UX를 유지했다.

text
홈
 ├── Article 미리보기
 ├── Article 미리보기
 └── Article 미리보기

하지만 검색엔진이 접근할 수 있는 별도의 상세 페이지도 만들었다.

text
홈
 │
 ├─ 빠르게 탐색 → 모달
 │
 └─ 저장 / 공유 / 검색 → 상세 페이지
                         ↓
                /ko/articles/{slug}

즉,

사용자 경험을 위한 모달과 검색엔진이 읽을 문서를 같은 구조에 억지로 넣지 않았다.

둘의 역할을 분리한 것이다.

12. 결국 SEO의 본질은 무엇이었을까?

이번 작업을 하면서 처음에는 Next.js의 SEO 기능들을 하나씩 추가하는 것처럼 보였다.

하지만 전체를 연결해보면 훨씬 단순하게 정리할 수 있다.

SEO에서 필요한 것POKIT에서 구현한 방법
고유한 페이지`/articles/{slug}`
콘텐츠가 포함된 HTMLApp Router + RSC
페이지별 제목/설명`generateMetadata`
검색엔진의 URL 발견`sitemap.ts`
크롤링 규칙`robots.ts`
대표 URL 지정`canonical`
다국어 관계 지정`hreflang`
기존 URL 연결middleware 308
공유 미리보기Open Graph / Twitter
콘텐츠 구조 표현Article JSON-LD
페이지 계층 표현Breadcrumb JSON-LD
미번역 페이지 제외`noindex`
빠른 콘텐츠 탐색홈 모달 유지

그리고 여기서 Next.js는 SEO 그 자체가 아니다.

Next.js는 이러한 작업을 쉽게 구현할 수 있는 도구를 제공할 뿐이다.

SEO의 핵심은 오히려 다음과 같이 정리할 수 있다.

① 검색엔진이 페이지를 발견할 수 있어야 한다

text
sitemap
내부 링크

② 페이지마다 고유한 URL이 있어야 한다

text
/articles/sleep-routine
/articles/morning-routine

③ 검색엔진이 페이지의 내용을 이해할 수 있어야 한다

text
title
description
HTML content
structured data

④ 여러 URL이나 언어가 있다면 관계를 명확하게 해야 한다

text
canonical
hreflang
redirect
noindex

⑤ 사용자가 검색결과를 클릭하고 공유할 수 있도록 만들어야 한다

text
Open Graph
Twitter Card

결국 SEO를 구현할 때는

“Next.js에서 SEO 기능을 어떻게 붙이지?”

라고 접근하기보다,

“검색엔진이 이 콘텐츠를 하나의 독립된 문서로 발견하고 이해할 수 있는가?”

를 먼저 생각하는 것이 더 중요하다.

Next.js의 generateMetadata, sitemap.ts, robots.ts, RSC 같은 기능은 결국 이 구조를 구현하기 위한 수단이다.

13. 이제부터는 실제 자연 유입을 분석해보려고 한다

현재 POKIT 웹사이트의 SEO 구조를 정리했다면 이제부터는 실제 결과를 확인할 차례다.

SEO는 기능을 구현했다고 끝나는 것이 아니라,

text
SEO 구조 개선
      ↓
검색엔진 크롤링
      ↓
색인
      ↓
검색 노출
      ↓
검색 클릭
      ↓
자연 유입
      ↓
콘텐츠 분석
      ↓
다시 개선

이 과정을 반복하면서 만들어가는 것이라고 생각한다.

앞으로는 매일 또는 일정한 주기로 검색 노출과 자연 유입 데이터를 확인하면서,

  • 어떤 검색어에서 노출되는지
  • 어떤 Article이 검색되는지
  • 어떤 페이지가 실제 유입을 만드는지
  • 노출은 되지만 클릭되지 않는 콘텐츠는 무엇인지
  • 어떤 콘텐츠를 추가해야 새로운 검색 유입을 만들 수 있는지
  • Google과 Naver에서 차이가 있는지

등을 직접 확인해보려고 한다.

이번 글은 그 분석을 시작하기 위한 현재 POKIT SEO 구조의 기준점이다.

이제부터는 이 기준을 바탕으로 실제 데이터를 보면서,

“POKIT의 자연 유입을 어떻게 만들어갈 수 있을까?”

를 하나씩 실험해보려고 한다.