[feat] SEO 최적화 - #235
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough루트 메타데이터·robots·sitemap 추가, 랜딩·검색·모임상세 전용 메타데이터 도입, 검색 기본 상태 기반 robots 제어, 동적 모임 상세 URL 수집 로직 및 일부 텍스트·레이아웃·환경설정 정리가 적용되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as "Client"
participant SitemapRoute as "app/sitemap"
participant SEOUtil as "getMoimDetailUrls()"
participant API as "api.meetings"
participant Router as "ROUTES / RESOLVED_SITE_URL"
Client->>SitemapRoute: GET /sitemap.xml
SitemapRoute->>SEOUtil: 호출 (getMoimDetailUrls)
SEOUtil->>API: getList(size=100, cursor)
API-->>SEOUtil: 페이지 응답 (items, nextCursor, hasMore)
SEOUtil->>SEOUtil: meeting.id → 절대 URL 생성 (RESOLVED_SITE_URL + ROUTES.moimDetail)
alt 다음 페이지 존재
SEOUtil->>API: 다음 페이지 요청 (nextCursor)
end
SEOUtil-->>SitemapRoute: URL 목록 반환
SitemapRoute->>Router: 정적 경로 결합 (RESOLVED_SITE_URL + ROUTES)
SitemapRoute-->>Client: sitemap entries (static + dynamic)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/_pages/space-search/constants.ts`:
- Around line 2-3: The title/description pair in constants (the description and
title properties) use inconsistent product terms ("스페이스" vs "모임"); update them
to the same approved term from the product terminology (e.g., change title from
"지금 모임에 참여해보세요" to "지금 스페이스에 참여해보세요" or adjust description to use "모임") so both
description and title use the same word and align with the space-search context;
apply the chosen term consistently in the description and title values and
verify it matches the product term policy.
In `@apps/web/src/_pages/space-search/lib/get-search-metadata.ts`:
- Around line 13-17: isDefaultSearchPage currently treats unknown query keys
(and multi-valued arrays) as harmless because parseSearchQueryState ignores
them; update isDefaultSearchPage to reject such cases by (1) building the set of
allowed keys from the parsed queryState (const queryState =
parseSearchQueryState(searchParams); const allowed = new
Set(Object.keys(queryState))) and then comparing against the raw searchParams
keys: if any key in searchParams is not in allowed, return false; and (2) if any
searchParams value is an array (Array.isArray) with length > 1 treat it as
non-default and return false; only if no unknown keys and no multi-valued arrays
proceed to compare buildSearchHref(ROUTES.search, queryState) === ROUTES.search.
Ensure you reference isDefaultSearchPage, parseSearchQueryState,
buildSearchHref, and ROUTES.search when making the change.
In `@apps/web/src/app/`(main)/moim-detail/[meetingId]/page.tsx:
- Around line 29-37: parseMeetingId currently permits numeric strings with
exponents, leading zeros or surrounding whitespace because it uses
Number(value); instead validate the raw path segment against a strict decimal
regex like /^[1-9]\d*$/ and call notFound() if it doesn't match, then safely
convert to Number (or parseInt) and return it; update the parseMeetingId
function to perform this regex check before any Number.isInteger/<=0 checks so
only pure non-zero-leading decimal IDs (e.g. "1","10","123") are accepted.
In `@apps/web/src/shared/seo/get-moim-detail-urls.ts`:
- Around line 45-47: The catch block that currently swallows errors in
getMoimDetailUrls should log the caught error before returning an empty array;
modify the catch to catch the error (e) and emit a minimal log or metric (e.g.,
console.error or the project logger like logger.error) with context such as
"getMoimDetailUrls failed" and the error, then return []; ensure you do not
change the return behavior—only add the single-line log and keep the function
name getMoimDetailUrls referenced for locating the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: af8c47a5-847f-4663-8b15-6b58313f4f3c
📒 Files selected for processing (16)
.gitignoreapps/web/src/_pages/landing/lib/landing-metadata.tsapps/web/src/_pages/moim-detail/lib/get-moim-detail-metadata.tsapps/web/src/_pages/space-search/constants.tsapps/web/src/_pages/space-search/lib/get-search-metadata.tsapps/web/src/app/(main)/(auth)/layout.tsxapps/web/src/app/(main)/moim-detail/[meetingId]/page.tsxapps/web/src/app/(main)/page.tsxapps/web/src/app/(main)/search/page.tsxapps/web/src/app/layout.tsxapps/web/src/app/robots.tsapps/web/src/app/sitemap.tsapps/web/src/proxy.tsapps/web/src/shared/config/env.tsapps/web/src/shared/config/site.tsapps/web/src/shared/seo/get-moim-detail-urls.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/_pages/space-search/lib/get-search-metadata.ts`:
- Around line 25-27: The current index check relies on normalized state so URLs
with invalid/empty query params (e.g., /search?category=invalid or
/search?keyword=) can be treated as the default; change get-search-metadata.ts
so it only returns index: true when the normalized href equals ROUTES.search AND
the original raw searchParams contains no query entries (i.e.,
searchParams.toString() === '' or URLSearchParams has no keys). Keep references
to parseSearchQueryState and buildSearchHref but add this additional guard on
the original searchParams to prevent false positives.
In `@apps/web/src/shared/seo/get-moim-detail-urls.ts`:
- Around line 28-42: The pagination loop using
visitedCursors/getMeetingsPage/buildMoimDetailUrl may run unbounded if an
upstream API keeps returning hasMore and new nextCursor values; add a hard cap
(e.g., maxPages or maxIterations) and increment a counter each iteration,
breaking the loop when the counter exceeds the cap (and optionally log a
warning) to prevent excessive calls; keep the existing loop logic
(visitedCursors, cursor assignment, meetingDetailUrls population) but insert the
counter check before continuing to the next getMeetingsPage call.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 623268ec-806f-4b42-8735-e0ff510f645d
📒 Files selected for processing (4)
apps/web/src/_pages/space-search/lib/get-search-metadata.tsapps/web/src/app/(main)/moim-detail/[meetingId]/page.tsxapps/web/src/app/(main)/search/page.tsxapps/web/src/shared/seo/get-moim-detail-urls.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/features/space-search/ui/space-search-keyword-bar.tsx (1)
59-64:⚠️ Potential issue | 🟡 Minor접근성 라벨이 placeholder와 불일치합니다
Line 60, Line 87의
aria-label이 아직 “스페이스 검색어 입력”이라, Line 63/Line 90의 “모임 …” 문구와 의미가 어긋납니다. 스크린리더 사용자에게 혼동을 줄 수 있어 동일 용어로 맞춰주세요.수정 예시
- aria-label="스페이스 검색어 입력" + aria-label="모임 검색어 입력" ... - aria-label="스페이스 검색어 입력" + aria-label="모임 검색어 입력"Also applies to: 86-90
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/space-search/ui/space-search-keyword-bar.tsx` around lines 59 - 64, The aria-label on the search input ("스페이스 검색어 입력") does not match the placeholder ("모임명, 설명, 카테고리, 종류 검색"); update the aria-label to the same descriptive text as the placeholder (or vice versa) so screen readers get consistent wording for the input that uses onChange/onKeywordChange; apply the same change to the other instance of this input (the repeated block referenced around the second occurrence) to keep both inputs consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/_pages/space-search/lib/get-search-metadata.ts`:
- Line 12: Replace the locally defined ALLOWED_SEARCH_PARAM_KEYS with a single
exported source: import and use the allowed-keys export from the module that
defines parseSearchQueryState (e.g., export the Set/array from the
parseSearchQueryState module and consume it here) so both the parser and
get-search-metadata use the same canonical allowed keys; update the import to
reference that exported symbol and remove the local const
ALLOWED_SEARCH_PARAM_KEYS.
In `@apps/web/src/features/space-search/ui/space-search-keyword-bar.tsx`:
- Around line 82-83: The form's Tailwind classes in the SpaceSearchKeywordBar
component conflict: using min-w-80 together with sm:w-[15.5rem] prevents the
small breakpoint width from taking effect. Update the class list on the <form>
(in SpaceSearchKeywordBar / the element using handleSubmit) to remove the
conflicting min-w-80 or replace it with a responsive min-width (e.g., min-w-0 or
sm:min-w-[15.5rem]) and keep w-full sm:w-[15.5rem] so the intended sm breakpoint
width is honored; ensure any references to className prop are preserved.
In `@apps/web/src/shared/seo/get-moim-detail-urls.ts`:
- Around line 53-55: The catch block in getMoimDetailUrls currently logs the
entire error object which may contain sensitive request metadata; change the
catch to log a minimal, non-sensitive message (e.g., "getMoimDetailUrls failed"
plus error.message or a sanitized error code) instead of the full error object,
or call a sanitization utility before logging; update the console.error(...)
call in the getMoimDetailUrls catch block to only include safe fields
(error.message or a short identifier) and/or use an existing logging helper that
strips headers and stack traces.
---
Outside diff comments:
In `@apps/web/src/features/space-search/ui/space-search-keyword-bar.tsx`:
- Around line 59-64: The aria-label on the search input ("스페이스 검색어 입력") does not
match the placeholder ("모임명, 설명, 카테고리, 종류 검색"); update the aria-label to the
same descriptive text as the placeholder (or vice versa) so screen readers get
consistent wording for the input that uses onChange/onKeywordChange; apply the
same change to the other instance of this input (the repeated block referenced
around the second occurrence) to keep both inputs consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 53e7a4e6-1094-4dc8-aa4a-4ffa6ca89961
📒 Files selected for processing (3)
apps/web/src/_pages/space-search/lib/get-search-metadata.tsapps/web/src/features/space-search/ui/space-search-keyword-bar.tsxapps/web/src/shared/seo/get-moim-detail-urls.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/features/space-search/ui/space-search-keyword-bar.tsx`:
- Line 63: The placeholder text in the input was changed to "모임..." but the
aria-label still uses "스페이스", causing inconsistent screen reader text; update
the aria-label to match the placeholder wording (use "모임" instead of "스페이스") in
the input elements within the SpaceSearchKeywordBar component (look for the JSX
input/combobox elements that set placeholder and aria-label) and apply the same
change for the other occurrence noted (the second input around the other
instance mentioned).
In `@apps/web/src/shared/seo/get-moim-detail-urls.ts`:
- Line 52: Return value ordering is unstable; sort the meetingDetailUrls before
returning to make sitemap output deterministic by replacing the current spread
return of meetingDetailUrls with a sorted array (e.g., call .sort() on the array
produced from meetingDetailUrls) in the get-moim-detail-urls function so the
output order is stable across API responses and caches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 91b77531-8042-40de-b3c7-10cadf71e777
📒 Files selected for processing (7)
apps/web/src/_pages/space-search/lib/get-search-metadata.tsapps/web/src/app/(main)/page.tsxapps/web/src/app/layout.tsxapps/web/src/features/space-search/model/search-params.tsapps/web/src/features/space-search/ui/space-search-keyword-bar.tsxapps/web/src/proxy.tsapps/web/src/shared/seo/get-moim-detail-urls.ts
| cursor = page.nextCursor; | ||
| } | ||
|
|
||
| return [...meetingDetailUrls]; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
sitemap 결과 정렬로 출력 안정성을 높여주세요.
현재는 API 응답 순서에 따라 URL 배열 순서가 달라질 수 있어, 캐시/스냅샷 비교 시 불필요한 변동이 생길 수 있습니다. 반환 전에 정렬하면 출력이 더 안정적입니다.
정렬 적용 제안
- return [...meetingDetailUrls];
+ return [...meetingDetailUrls].sort();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return [...meetingDetailUrls]; | |
| return [...meetingDetailUrls].sort(); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/shared/seo/get-moim-detail-urls.ts` at line 52, Return value
ordering is unstable; sort the meetingDetailUrls before returning to make
sitemap output deterministic by replacing the current spread return of
meetingDetailUrls with a sorted array (e.g., call .sort() on the array produced
from meetingDetailUrls) in the get-moim-detail-urls function so the output order
is stable across API responses and caches.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/features/space-search/ui/space-search-toolbar.tsx`:
- Line 107: 컨테이너의 현재 클래스 조합("flex-nowrap" + "overflow-hidden") 때문에 모바일(≈320px)에서
마지막 필터가 잘릴 수 있으니, 공간 검색 툴바의 컨테이너 in space-search-toolbar.tsx에서 "flex-nowrap"를
제거하거나 기본을 "flex-wrap"으로 바꾸고 "overflow-hidden"을 삭제(또는 기본을 "overflow-visible"로
변경)하여 작은 화면에서 항목이 줄바꿈되게 하세요; 또한 필요하면 packages/ui/src/components/ui/filter.tsx의
Filter 컴포넌트에서 "whitespace-nowrap"를 제거하거나 좁은 화면용으로 반응형으로 바꿔(예:
sm:whitespace-nowrap 대신 기본은 normal, sm:whitespace-nowrap) 모바일 320px에서 긴 옵션 조합으로도
레이블이 잘리지 않는지 확인해 주세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d2ccc3af-b8b5-4f9b-b3d0-0db8ad546f8d
📒 Files selected for processing (7)
apps/web/src/_pages/moim-detail/ui/information-container.tsxapps/web/src/_pages/space-search/ui/space-search-hero.tsxapps/web/src/entities/moim-detail/model/mapper.tsapps/web/src/features/space-search/ui/space-card-skeleton.tsxapps/web/src/features/space-search/ui/space-card.tsxapps/web/src/features/space-search/ui/space-search-sections.tsxapps/web/src/features/space-search/ui/space-search-toolbar.tsx
📌 Summary
프로젝트 전반의 SEO 기본기를 정비했습니다.
++핫픽스 사항들 PR에 반영중입니다. (완료)
기본 metadata,
robots.txt,sitemap.xml을 추가하고, 공개 페이지와 비공개/개인화 페이지를 검색엔진 관점에서 구분하도록 정책을 정리했습니다.또한
/,/search,/moim-detail/[meetingId]에 전용 metadata를 적용해 페이지 목적에 맞는 title/description/canonical이 내려가도록 보완했습니다.📄 Tasks
루트 metadata 기본값 정리
src/wep/app/layout.tsx에 기본 메타데이터 정의title.template적용으로 페이지별 title 체인 일관화robots.ts/sitemap.ts추가robots.tssitemap.tsrobots.ts,sitemap.ts추가/robots.txt,/sitemap.xml이 proxy 인증 로직에 막히지 않도록 예외 처리disallow와noindex를 역할에 맞게 분리 적용Disallow는 크롤링 자체를 허용하지 않는 목적의 경로에 적용 -> 접근에 인증이 필요한 페이지 or login, signup/api/,/oauth/,/mypage,/spaces,/moim-create,/moim-editnoindex는 HTML 페이지의 검색 결과 노출을 직접 제어하는 용도로 적용/login,/signup)는 공통 auth layout에서noindex처리했습니다./search는 기본 URL만 index 허용하고, 필터/키워드가 붙은 내부 탐색 상태는 canonical을/search로 고정하면서noindex처리했습니다.동적 meetingId sitemap 수집 추가
/moim-detail/[meetingId]는 공개된 상세 페이지이므로 sitemap 대상에 포함했습니다./moim-detail경로만 넣는 경우 실제 존재하지 않는 URL을 index 대상으로 내보낼 수 있어, meetings 목록 API를 순회해 실제meetingId를 수집한 뒤/moim-detail/{id}URL을 생성하도록 수정했습니다.shared/seo/get-moim-detail-urls.ts로 분리해sitemap.ts는 정적 URL 조합과 merge만 담당하도록 정리했습니다.revalidate를 적용해 크롤러가 sitemap 요청 시마다 전체 목록을 매번 다시 순회하지 않도록 했습니다.전용 metadata 적용 페이지
메인 랜딩 페이지
/search/search, 쿼리 상태noindex/moim-detail/[meetingId]메타데이터 로직 위치 정리
lib폴더에 분리했습니다./search는 검색 상태 기반 canonical/noindex 정책이 핵심이고,/moim-detail/[meetingId]는 상세 데이터 기반 title/description/OG 생성이 핵심이라 성격이 달라 공통 추상화보다 페이지 근처에서 관리하는 쪽이 더 읽기 쉽고 유지보수하기 적절하다고 판단했습니다.기타 HOTFIX
모임상세페이지에서모집 중인 모임도 비로그인 유저에겐모집 마감이라고 보이던 현상 수정했습니다.👀 To Reviewer
📸 Screenshot
/robots.txt출력 화면 (disallow 위주)sitemap.xml의 주소를 안내합니다./sitemap.xml출력 화면 (allow 위주)/searchhead/moim-detail/[meetingId]head (모임별 title | 기본 head)Summary by CodeRabbit
새로운 기능
Chores
기타