Skip to content

fix(seo): noindex 페이지에 JSON-LD 구조화 데이터 미렌더#57

Merged
CaesiumY merged 2 commits into
mainfrom
focused-elbakyan-8e16a2
May 21, 2026
Merged

fix(seo): noindex 페이지에 JSON-LD 구조화 데이터 미렌더#57
CaesiumY merged 2 commits into
mainfrom
focused-elbakyan-8e16a2

Conversation

@CaesiumY
Copy link
Copy Markdown
Owner

Description

pageType="profile"을 전달하는 레이아웃은 AboutLayout(/about)과 PortfolioLayout(/portfolio) 두 개입니다. Layout.astro의 Person JSON-LD는 url${SITE.website}/about로 고정돼 있어, noindex 페이지인 /portfolio에도 Person 구조화 데이터가 렌더되고 url이 잘못 /about을 가리켰습니다.

robots prop에 noindex가 포함되면 JSON-LD 자체를 렌더하지 않도록 게이팅했습니다. 검색엔진은 색인하지 않을 페이지의 구조화 데이터를 사용하지 않으므로, /portfolio 전용 분기가 아닌 일반 규칙으로 처리해 향후 추가될 noindex 페이지에도 자동 적용됩니다.

변경 파일: src/layouts/Layout.astro (단일 파일, +16 / −7)

Types of changes

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Documentation Update (if none of the other choices apply)
  • Others (any other types not listed above)

Checklist

  • I have read the Contributing Guide
  • I have added the necessary documentation (if appropriate)
  • Breaking Change (fix or feature that would cause existing functionality to not work as expected)

Further comments

검토한 세 가지 방안:

  • url을 prop으로 전달: /portfolio가 여전히 Person 스키마를 렌더하므로 urlnoindex 페이지를 가리키는 의미적 모순이 남습니다.
  • canonical 기반 동적 설정: /portfolio에서 url/portfolio가 되어 동일한 문제. schema.org Person.url은 "그 인물의 정규 프로필 페이지"를 의미하므로 현재 페이지 canonical과 개념이 다릅니다.
  • 채택 — noindex 페이지에서 구조화 데이터 미렌더: /portfoliorobots: "noindex, nofollow"라 구조화 데이터의 SEO 가치가 0입니다.

구현 노트:

  • structuredDatanull일 때 <script> 엘리먼트 자체를 조건부 렌더 — JSON.stringify(null)"null" 문자열을 출력하는 것을 방지.
  • Person.url${SITE.website}/about 고정값을 유지(사이트 차원의 정규 프로필 URL). 다음 수정자가 canonicalURL로 바꿔 버그를 재도입하지 않도록 의도 주석을 추가.

검증

  • pnpm build 통과(164 페이지 빌드, astro check 타입체크 통과), pnpm lint·pnpm format 통과
  • dist/about/index.html: JSON-LD Person 존재, "url":"https://caesiumy.dev/about" — 변경 전과 동일
  • dist/portfolio/index.html: application/ld+json 0건(제거됨), og:type="profile"·robots="noindex, nofollow" 메타는 영향 없이 유지

Related Issue

해당 없음

pageType="profile"을 전달하는 레이아웃은 AboutLayout(/about)과
PortfolioLayout(/portfolio) 두 개다. Person JSON-LD의 url이
${SITE.website}/about로 고정돼 있어, noindex 페이지인 /portfolio에도
Person 구조화 데이터가 렌더되고 url이 잘못 /about을 가리켰다.

robots prop에 noindex가 포함되면 JSON-LD를 렌더하지 않도록 게이팅했다.
검색엔진은 색인하지 않을 페이지의 구조화 데이터를 사용하지 않으므로,
portfolio 전용 분기가 아닌 일반 규칙으로 처리해 향후 추가될 noindex
페이지에도 자동 적용된다. /about의 출력은 변경되지 않는다.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request modifies src/layouts/Layout.astro to prevent the generation of JSON-LD structured data on pages where the robots meta tag is set to 'noindex'. It also adds a clarifying comment regarding the author's profile URL. The review feedback suggests improving the detection of the 'noindex' state by using a case-insensitive regular expression that also accounts for the 'none' keyword, ensuring more robust behavior across different meta tag configurations.

Comment thread src/layouts/Layout.astro Outdated
HTML robots 디렉티브는 대소문자를 구분하지 않으며 none은
noindex + nofollow와 동등하다. 기존 includes("noindex")는 none이나
대문자 표기를 놓쳐, 그런 robots 값을 쓴 페이지가 크롤러에는
noindex이면서 구조화 데이터는 계속 렌더되는 불일치가 있었다.

robots 값은 meta 태그에 그대로 출력되므로, 동일 기준으로 noindex를
판정하도록 /noindex|none/i 정규식으로 교체한다.
@claude
Copy link
Copy Markdown

claude Bot commented May 20, 2026

Code Review — PR #57

Overview

이 PR은 noindex 지시자가 설정된 페이지(현재는 /portfolio)에서 JSON-LD 구조화 데이터가 불필요하게 렌더되던 문제를 수정합니다. Person 스키마의 url/about으로 하드코딩되어 있어 /portfolio에서 렌더될 경우 의미적으로 잘못된 구조화 데이터가 삽입되었으나, 이번 수정으로 noindex 페이지에서 구조화 데이터 자체를 생략하도록 처리했습니다.


Code Quality & Style ✅

  • 조건부 렌더링 패턴: {structuredData && (<script ... />)} 는 Astro의 관용적 조건부 렌더링 방식이며 올바릅니다.
  • JSON.stringify(null) 방어: structuredDatanull일 때 <script> 자체를 생략하여 "null" 문자열이 인라인 스크립트로 출력되는 부작용을 제대로 차단합니다.
  • 주석 품질: Person.url이 현재 canonical이 아닌 고정값임을 명시한 주석은 향후 유지보수 시 버그 재도입을 방지하는 좋은 방어적 문서화입니다.
  • 일반화 처리: /portfolio만 특케이스로 처리하지 않고 noindex 여부를 공통 규칙으로 추출한 점이 좋습니다 — 향후 noindex 페이지 추가 시 자동으로 적용됩니다.

Potential Issues

1. Regex 경계 처리 (Minor)

const noindex = /noindex|none/i.test(robots ?? "");
  • none이 다른 robots 값에 부분 문자열로 포함될 가능성이 낮지만, 엄밀히 하려면 단어 경계나 쉼표 기준 토큰 분리를 고려할 수 있습니다:
    const noindex = robots?.split(",").map(d => d.trim().toLowerCase())
                           .some(d => d === "noindex" || d === "none") ?? false;
  • 실제 robots 지시자는 표준 값만 사용하므로 현재 구현이 실용적으로 문제는 없습니다.

2. robots prop 타입 확인 (Info)

  • robots ?? ""로 null/undefined를 처리하는 것은 적절합니다. robots prop이 선택적(string | undefined)으로 정의되어 있다면 현재 코드가 완전히 안전합니다.

Performance ✅

  • 구조화 데이터 스크립트 태그를 조건부로 생략하므로 noindex 페이지의 HTML 크기가 약간 줄어드는 긍정적 효과가 있습니다. 빌드 타임 영향은 무시할 수준입니다.

Security ✅

  • 별도 보안 우려사항 없음. set:htmlJSON.stringify로 직렬화된 정적 데이터를 처리하므로 XSS 위험 없습니다.

Verification ✅

PR 설명에 따르면 빌드 검증이 CLAUDE.md의 가이드라인(pnpm builddist/ 폴더 확인)에 맞게 수행되었습니다:

  • dist/about/index.html: JSON-LD Person 정상 존재
  • dist/portfolio/index.html: application/ld+json 제거 확인

Summary

변경 범위가 단일 파일, 최소 수정(+17/-7)이고, 수정 목적과 접근 방식이 명확하게 문서화되어 있습니다. 버그 수정으로서 적절하며 회귀 위험이 낮습니다. Regex 경계 처리는 선택적 개선사항으로, 현재 구현도 충분히 안전합니다.

Approve 권고합니다.

@CaesiumY
Copy link
Copy Markdown
Owner Author

리뷰 감사합니다. 두 지적사항 검토 결과를 공유합니다.

1. Regex 경계 처리 — 현재 /noindex|none/i 유지

표준 robots 디렉티브(noindex, nofollow, none, noarchive, nosnippet, noimageindex, notranslate, unavailable_after, nositelinkssearchbox 등) 전체를 점검한 결과, 어디에도 none·noindex가 오인될 만한 부분 문자열로 들어있지 않아 유효한 입력에서는 오탐이 발생하지 않습니다. robots는 사이트 소유자가 작성하는 controlled-vocabulary frontmatter라 비표준 값이 유입될 여지도 낮습니다. 토큰 분리 버전은 동일한 동작에 코드량만 늘어, 직전 리뷰에서 합의된 정규식 형태를 유지하는 편이 가독성 면에서 낫다고 판단했습니다(리뷰에서도 "선택적 개선사항"으로 명시).

2. robots prop 타입 — 이미 안전

Layout.astro Props에 robots?: string(optional)으로 정의돼 있어 robots ?? ""는 완전히 안전합니다. 추가 조치가 필요하지 않습니다.

→ 코드 변경 없이 현 상태로 머지 가능합니다.

@CaesiumY CaesiumY merged commit 206c0a4 into main May 21, 2026
5 checks passed
@CaesiumY CaesiumY deleted the focused-elbakyan-8e16a2 branch May 21, 2026 11:41
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.

1 participant