Skip to content

[공통] 페이지 단위 SEO 메타데이터 인프라 추가#1257

Merged
dooohun merged 1 commit into
developfrom
feat/#1256/seo
May 11, 2026
Merged

[공통] 페이지 단위 SEO 메타데이터 인프라 추가#1257
dooohun merged 1 commit into
developfrom
feat/#1256/seo

Conversation

@dooohun
Copy link
Copy Markdown
Contributor

@dooohun dooohun commented May 11, 2026

What is this PR? 🔍

Changes 📝

  • src/static/seo.ts — SEO 상수 + buildAbsoluteUrl(path) 헬퍼 (canonical/og:url은 KOIN_BASE_URL 기반, stage/prod 자동 분기)
  • src/components/seo/Seo/index.tsx — title, description, url, image, type, noindex, imageAlt props 지원. 모든 meta에 key 부여로 페이지 단위 override 가능

수정 파일

  • _document.tsx — 페이지별 메타 제거, 셸 전용 항목만 유지
  • _app.tsx — <title></title> → . Component.title fallback 'KOIN' → undefined 로 변경해 DEFAULT_TITLE 적용

호환성

  • 기존 Component.title 패턴 유지, 후속 PR에서 페이지별 도입 시 자동 우선 적용
  • 모든 페이지 <title>: 'KOIN' → '코인 - 한기대 커뮤니티' 로 일괄 개선

ScreenShot 📷

N/A

Test CheckList ✅

  • yarn lint:eslint
  • yarn tsc --noEmit

Precaution

  • 인프라만 도입, 페이지별 메타 적용은 후속 PR에서 진행
  • 단독 머지 시 미적용 페이지는 default 값으로 노출 (기존과 동일 수준)

✔️ Please check if the PR fulfills these requirements

  • It's submitted to the correct branch, not the develop branch unconditionally?
  • If on a hotfix branch, ensure it targets main?
  • There are no warning message when you run yarn lint

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 페이지 메타데이터 설정 컴포넌트 추가
    • 페이지 제목, 설명, 이미지 메타데이터 관리 지원
    • Open Graph 및 Twitter 카드 태그 자동 생성
    • 검색 엔진 인덱싱 제어 옵션 추가
    • URL 기반 절대 경로 자동 생성 기능 포함

Review Change Stack

- src/static/seo.ts: SITE_NAME, DEFAULT_TITLE, DEFAULT_DESCRIPTION,
  DEFAULT_OG_IMAGE, TWITTER_CARD_TYPE 상수와 buildAbsoluteUrl 헬퍼 추가
- src/components/seo/Seo/index.tsx: title, description, canonical,
  Open Graph, Twitter Card, noindex 옵션을 처리하는 공통 컴포넌트 추가.
  메타 태그에 key 부여로 페이지 단위 override 동작 보장
- src/pages/_document.tsx: 페이지별로 변동되는 메타(description,
  og:title, og:description, og:image, og:image:width) 제거.
  Seo 컴포넌트가 담당
- src/pages/_app.tsx: <Head><title>을 <Seo title={pageTitle} />로 교체.
  Component.title fallback을 'KOIN'에서 undefined로 변경하여
  Seo의 DEFAULT_TITLE이 적용되도록 조정
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Walkthrough

새로운 Seo 컴포넌트와 SEO 상수 모듈을 도입하여 페이지별 메타데이터(제목, 설명, 정규 URL, OpenGraph, Twitter Card)를 동적으로 관리하고, 기존의 정적 문서 수준 메타 태그를 페이지별 공통 추상화로 통합한다.

Changes

SEO 메타데이터 인프라

Layer / File(s) Summary
SEO 상수 및 URL 빌더
src/static/seo.ts
사이트 이름, 기본 제목/설명, OpenGraph 이미지, Twitter 카드 타입 상수와 상대/절대 URL을 통합 URL로 변환하는 buildAbsoluteUrl 헬퍼 함수 추가.
Seo 컴포넌트 정의
src/components/seo/Seo/index.tsx
SeoProps 인터페이스 정의 및 필요한 임포트 추가.
Seo 컴포넌트 로직
src/components/seo/Seo/index.tsx
기본값 해석, Router를 통한 정규 URL 계산(query 제외), 라우팅 데이터 추출 구현.
Seo 메타 태그 렌더링
src/components/seo/Seo/index.tsx
<Head> 내 제목, 설명, 정규 링크, robots noindex, OpenGraph, Twitter Card 메타 태그를 조건부로 렌더링.
_app.tsx Seo 통합
src/pages/_app.tsx
Seo 컴포넌트 임포트 추가, pageTitle 로직 수정(미제공 시 undefined), 기존 title 렌더링을 <Seo title={pageTitle} />로 교체.
_document.tsx 정리
src/pages/_document.tsx
정적 OpenGraph 메타 태그(og:title, og:description, og:image, og:image:width)와 설명 메타 태그 제거, 기본 메타만 유지(charset, theme-color, author).

🎯 2 (Simple) | ⏱️ ~12분

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항(페이지 단위 SEO 메타데이터 인프라 추가)을 명확하고 간결하게 설명하고 있습니다.
Linked Issues check ✅ Passed PR이 이슈 #1256의 첫 번째 단계 목표인 공통 Seo 컴포넌트 도입 및 _document 정리를 완벽하게 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 SEO 인프라 구축 범위 내에 있으며, 추가로 unrelated된 변경은 없습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#1256/seo

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dooohun dooohun self-assigned this May 11, 2026
@github-actions github-actions Bot requested review from ParkSungju01 and ff1451 May 11, 2026 06:39
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/components/seo/Seo/index.tsx (3)

22-30: 💤 Low value

함수 컴포넌트에 명시적 반환 타입을 추가하면 타입 안정성이 향상됩니다.

TypeScript best practice에 따라 컴포넌트의 반환 타입을 명시하는 것을 권장합니다.

♻️ 제안하는 수정
-export default function Seo({
+export default function Seo({
   title,
   description = DEFAULT_DESCRIPTION,
   url,
   image = DEFAULT_OG_IMAGE,
   type = 'website',
   noindex = false,
   imageAlt,
-}: SeoProps) {
+}: SeoProps): JSX.Element {

As per coding guidelines: Next.js, React, TypeScript 팀 코드 컨벤션 및 공식 스타일 가이드(React/TS best practices)를 우선적으로 반영하여, 가독성·안정성을 검토해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/seo/Seo/index.tsx` around lines 22 - 30, The Seo function
component currently lacks an explicit return type; update the declaration of Seo
to include a React return type (e.g., add ": JSX.Element" or ":
React.ReactElement" after the parameter list) to improve type safety and
clarity, ensuring the component signature (function Seo(...) : JSX.Element)
still uses the existing SeoProps param and that any necessary React types are
imported where used.

12-20: 💤 Low value

SeoProps 인터페이스를 export하면 재사용성이 향상됩니다.

다른 컴포넌트에서 SEO 관련 props 타입을 참조해야 할 경우를 대비해 인터페이스를 export하는 것을 고려해보세요.

♻️ 제안하는 수정
-interface SeoProps {
+export interface SeoProps {
   title?: string;
   description?: string;
   url?: string;
   image?: string;
   type?: 'website' | 'article';
   noindex?: boolean;
   imageAlt?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/seo/Seo/index.tsx` around lines 12 - 20, The SeoProps
interface is currently unexported which prevents reuse; update the declaration
for SeoProps (the interface named SeoProps in this file) to be exported (e.g.,
export interface SeoProps ...) so other components can import and reuse the
type, and then update any consumer files to import { SeoProps } from this module
as needed.

33-33: ⚡ Quick win

canonical URL에서 해시 프래그먼트(#)도 제거해야 합니다.

현재 구현은 쿼리 파라미터(?)만 제거하지만, router.asPath는 해시 프래그먼트(#section)도 포함할 수 있습니다. RFC에 따르면 canonical URL에는 프래그먼트를 포함하지 않아야 합니다.

♻️ 제안하는 수정
-  const canonicalUrl = buildAbsoluteUrl(url ?? router.asPath?.split('?')[0]);
+  const canonicalUrl = buildAbsoluteUrl(url ?? router.asPath?.split('?')[0]?.split('#')[0]);

또는 더 명확하게:

-  const canonicalUrl = buildAbsoluteUrl(url ?? router.asPath?.split('?')[0]);
+  const pathWithoutQuery = router.asPath?.split('?')[0] ?? '';
+  const pathWithoutFragment = pathWithoutQuery.split('#')[0];
+  const canonicalUrl = buildAbsoluteUrl(url ?? pathWithoutFragment);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/seo/Seo/index.tsx` at line 33, 현재 canonicalUrl 생성에서 쿼리만 제거하고
해시 프래그먼트(`#`)는 제거하지 않으므로 canonical에 프래그먼트가 포함될 수 있습니다; 수정하려면 Seo component의
canonicalUrl 계산(참조: canonicalUrl, buildAbsoluteUrl, router.asPath)을 변경해
router.asPath에서 쿼리와 해시를 모두 제거하도록 하거나 더 안전하게는 URL 파서(new URL(..., origin) 또는 URL
constructor)를 사용해 search와 hash를 빈값으로 설정한 뒤 buildAbsoluteUrl에 전달하여 프래그먼트를 완전히
제거하세요.
src/static/seo.ts (1)

10-15: ⚡ Quick win

타입 안정성을 위해 명시적 반환 타입 추가를 권장합니다.

TypeScript best practice에 따라 함수의 반환 타입을 명시하면 코드 가독성과 타입 추론 성능이 향상됩니다.

♻️ 제안하는 수정
-export const buildAbsoluteUrl = (path?: string) => {
+export const buildAbsoluteUrl = (path?: string): string => {
   if (!path) return KOIN_BASE_URL;
   if (/^https?:\/\//.test(path)) return path;
   const normalized = path.startsWith('/') ? path : `/${path}`;
   return `${KOIN_BASE_URL}${normalized}`;
 };

As per coding guidelines: Next.js, React, TypeScript 팀 코드 컨벤션 및 공식 스타일 가이드(React/TS best practices)를 우선적으로 반영하여, 가독성·안정성을 검토해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/static/seo.ts` around lines 10 - 15, The function buildAbsoluteUrl lacks
an explicit return type; update its signature to declare a return type of string
(e.g. export const buildAbsoluteUrl = (path?: string): string => ...) and ensure
any referenced constant like KOIN_BASE_URL is typed as string so the compiler
can verify all return branches produce a string.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/seo/Seo/index.tsx`:
- Around line 22-30: The Seo function component currently lacks an explicit
return type; update the declaration of Seo to include a React return type (e.g.,
add ": JSX.Element" or ": React.ReactElement" after the parameter list) to
improve type safety and clarity, ensuring the component signature (function
Seo(...) : JSX.Element) still uses the existing SeoProps param and that any
necessary React types are imported where used.
- Around line 12-20: The SeoProps interface is currently unexported which
prevents reuse; update the declaration for SeoProps (the interface named
SeoProps in this file) to be exported (e.g., export interface SeoProps ...) so
other components can import and reuse the type, and then update any consumer
files to import { SeoProps } from this module as needed.
- Line 33: 현재 canonicalUrl 생성에서 쿼리만 제거하고 해시 프래그먼트(`#`)는 제거하지 않으므로 canonical에
프래그먼트가 포함될 수 있습니다; 수정하려면 Seo component의 canonicalUrl 계산(참조: canonicalUrl,
buildAbsoluteUrl, router.asPath)을 변경해 router.asPath에서 쿼리와 해시를 모두 제거하도록 하거나 더
안전하게는 URL 파서(new URL(..., origin) 또는 URL constructor)를 사용해 search와 hash를 빈값으로
설정한 뒤 buildAbsoluteUrl에 전달하여 프래그먼트를 완전히 제거하세요.

In `@src/static/seo.ts`:
- Around line 10-15: The function buildAbsoluteUrl lacks an explicit return
type; update its signature to declare a return type of string (e.g. export const
buildAbsoluteUrl = (path?: string): string => ...) and ensure any referenced
constant like KOIN_BASE_URL is typed as string so the compiler can verify all
return branches produce a string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4bcedc8-fdd9-49e0-b470-0f06b1e32a91

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae4a8b and eefb400.

📒 Files selected for processing (4)
  • src/components/seo/Seo/index.tsx
  • src/pages/_app.tsx
  • src/pages/_document.tsx
  • src/static/seo.ts

Copy link
Copy Markdown
Contributor

@ParkSungju01 ParkSungju01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metadata 방식으로 변경하니 SEO 관리가 더 명확해진 것 같네요. 수고하셨습니다!

@dooohun dooohun merged commit 8ac3601 into develop May 11, 2026
5 checks passed
@github-actions github-actions Bot deleted the feat/#1256/seo branch May 11, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants