Skip to content

Conversation

@manNomi
Copy link
Contributor

@manNomi manNomi commented Dec 12, 2025

관련 이슈

  • resolves: #이슈 번호

작업 내용

  • 홈페이지 seo 개선을 위한 스트럭처 데이터 추가했습니다

리뷰요구사항

  • 과한 seo 관리나 증복 가능성 또는 저해요소 판단이 필요합니다

@manNomi manNomi requested a review from wibaek as a code owner December 12, 2025 00:08
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

개요

홈페이지 컴포넌트에서 SEO 최적화 및 레이아웃 재구성이 진행되었습니다. 다음 내용이 추가되었습니다:

  1. SEO 설정(baseUrl, ogImageUrl)과 확장된 메타데이터(제목, 설명, OpenGraph, Twitter)
  2. 웹사이트 구조화 데이터(JSON-LD) 및 스크립트 태그 주입
  3. PopularUniversitySection과 UniversityList 컴포넌트를 활용한 새로운 레이아웃
  4. 새로운 테두리/패딩 컨테이너 및 뉴스 섹션 추가

기존 데이터 페치(newsList, recommendedUniversities, allRegionsUniversityList)는 유지되지만, 렌더링 흐름이 새로운 섹션에 맞게 변경되었습니다.

코드 리뷰 예상 소요 시간

🎯 3 (Moderate) | ⏱️ ~20-25분

  • 주의깊게 검토할 영역:
    • JSON-LD 구조화 데이터 형식의 정확성 및 SEO 표준 준수 여부
    • 메타데이터와 OpenGraph 속성값의 타입 안정성
    • PopularUniversitySection 및 UniversityList 컴포넌트 통합 부분의 렌더링 로직
    • 레이아웃 재구성으로 인한 기존 데이터 페치 흐름 검증
    • 컴포넌트 조합의 의도된 순서 및 접근성

검토 제안자

  • wibaek

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive PR 설명이 필수 섹션을 포함하고 있으나, 관련 이슈 번호가 작성되지 않았고 작업 내용이 간략하며 특이 사항과 상세한 리뷰 요구사항이 부족합니다. 관련 이슈 번호를 명시하고, 작업 내용에서 메타데이터 확장, JSON-LD 스트럭처 데이터, 레이아웃 재조직 등 세부 사항을 추가하며, 특이 사항과 리뷰 요구사항을 더욱 구체적으로 작성해주세요.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 풀 리퀘스트 제목이 변경사항의 핵심인 SEO 스트럭처 데이터 추가를 명확하게 요약하고 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/app/(home)/page.tsx (1)

23-53: 2) 메타데이터 구성은 깔끔해졌고, “중복”은 문제될 수준은 아니에요.

  • title/description을 OG/Twitter에 반복 세팅하는 건 일반적인 패턴이라 “과한 SEO 관리”로 보이진 않아요.
  • 다만 문자열이 길어지기 쉬우니(특히 title) 실제 SERP/공유 카드에서 잘리는지 정도만 한번 확인하면 충분해요.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3d3f02 and 443f323.

📒 Files selected for processing (1)
  • src/app/(home)/page.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 242
File: src/components/ui/TabSelector.tsx:10-11
Timestamp: 2025-08-12T09:41:44.182Z
Learning: manNomi prefers to keep reusable UI components simple and focused on core functionality rather than adding many features. They don't want to over-engineer flexible/reusable UI components at the initial stage.
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/components/mentor/MentorChatCard/index.tsx:17-21
Timestamp: 2025-08-24T11:14:34.297Z
Learning: manNomi prefers not to receive accessibility suggestions or recommendations during code reviews.
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/api/auth/client/usePostLogout.ts:17-33
Timestamp: 2025-08-24T11:13:08.477Z
Learning: manNomi prefers to prioritize user experience over perfect state consistency in auth flows. Specifically, in logout scenarios, they prefer to keep tokens intact on API failure to avoid forcing users to re-login, even if it means temporary UI state inconsistency.
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/api/auth/client/useDeleteUserAccount.ts:17-27
Timestamp: 2025-08-24T11:11:40.758Z
Learning: manNomi prefers optimistic navigation in auth-related flows (like account deletion) to prevent race conditions where token clearing before navigation could cause intermediate redirects to login page due to pending async requests failing.
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 242
File: src/types/mentor.ts:70-73
Timestamp: 2025-08-12T04:07:04.134Z
Learning: manNomi prefers using Korean labels directly in enum values (e.g., MentorTab.MY_MENTEE = "나의 멘티") over separating enum keys from UI labels for efficiency and intuitiveness, considering scalability concerns as excessive for their current project needs.
🧬 Code graph analysis (1)
src/app/(home)/page.tsx (1)
public/svgs/home/index.ts (4)
  • IconMagnifyingGlass (14-14)
  • IconPaper (16-16)
  • IconMuseum (15-15)
  • IconIdCard (13-13)
🪛 ast-grep (0.40.0)
src/app/(home)/page.tsx

[warning] 77-77: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
src/app/(home)/page.tsx

[error] 91-91: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (2)
src/app/(home)/page.tsx (2)

55-67: 구조화 데이터의 쿼리 키(searchText)가 실제 라우트와 일치합니다.

검색 구현을 확인한 결과, SearchBar 컴포넌트와 SearchResultsContent 모두에서 searchText 매개변수를 일관되게 사용하고 있습니다.

  1. 쿼리 생성: SearchBar.tsx에서 queryParams.append("searchText", data.searchText.trim())로 검색 쿼리를 생성합니다.
  2. 쿼리 읽기: SearchResultsContent.tsx에서 searchParams.get("searchText")로 검색 매개변수를 읽습니다.

따라서 구조화 데이터의 target: /university?searchText={search_term_string}은 실제 구현과 완벽하게 일치하므로 추가 수정이 불필요합니다.


69-75: 이 검토 의견은 대상 지정이 부정확합니다.

  • "실시간" 레이블은 **145번 줄의 뉴스 섹션이 아니라 137번 줄의 PopularUniversitySection("실시간 인기있는 파견학교")**에 있습니다.
  • 직렬화 문제는 발견되지 않습니다: fetchAllNews()는 오직 기본 타입만 반환합니다 (id: number, title/description/imageUrl/url: string). News 인터페이스에는 Date/Timestamp 필드가 없고, NewsSectionDynamic으로 전달되는 newsList는 모두 직렬화 가능한 데이터입니다.
  • revalidate = 60 * 60 * 24 (24시간)는 전체 페이지에 적용되며, 현재 뉴스 섹션 구현에서는 직렬화 관련 런타임 에러가 발생하지 않습니다.

Likely an incorrect or invalid review comment.

Comment on lines +20 to +22
const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com";
const ogImageUrl = `${baseUrl}/opengraph-image.png`;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the relevant file to see current state
head -n 75 src/app/(home)/page.tsx | tail -n +15

Repository: solid-connection/solid-connect-web

Length of output: 213


🏁 Script executed:

# Search for NEXT_PUBLIC_WEB_URL usage across the codebase
rg "NEXT_PUBLIC_WEB_URL" --type ts --type tsx

Repository: solid-connection/solid-connect-web

Length of output: 105


🏁 Script executed:

# Search for environment files or configuration
fd -e env -e "env.*" | head -20

Repository: solid-connection/solid-connect-web

Length of output: 60


🏁 Script executed:

# Check for URL construction patterns (canonical, OG) in the file
rg "canonical|og|openGraph" src/app/\(home\)/page.tsx -A 2 -B 2

Repository: solid-connection/solid-connect-web

Length of output: 724


🏁 Script executed:

# Search for baseUrl usage throughout the file
rg "baseUrl" 'src/app/(home)/page.tsx' -n

Repository: solid-connection/solid-connect-web

Length of output: 376


🏁 Script executed:

# Check environment configuration files
find . -type f \( -name ".env*" -o -name "*.config.*" \) | grep -v node_modules | head -20

Repository: solid-connection/solid-connect-web

Length of output: 256


🏁 Script executed:

# Search for where NEXT_PUBLIC_WEB_URL might be set
rg "NEXT_PUBLIC_WEB_URL" . --type ts --type js -B 2 -A 2

Repository: solid-connection/solid-connect-web

Length of output: 984


🏁 Script executed:

# Check if there's any URL normalization utility already in use
rg "replace.*/$|removeTrailing|normaliz.*url" . --type ts --type js

Repository: solid-connection/solid-connect-web

Length of output: 60


🏁 Script executed:

# Read environment files to check for trailing slashes in NEXT_PUBLIC_WEB_URL
cat .env .env.development .env.production 2>/dev/null | grep NEXT_PUBLIC_WEB_URL

Repository: solid-connection/solid-connect-web

Length of output: 233


🏁 Script executed:

# Check for any other direct process.env.NEXT_PUBLIC_WEB_URL usage that might have same issue
rg "process\.env\.NEXT_PUBLIC_WEB_URL" . --type ts --type js -B 1 -A 1

Repository: solid-connection/solid-connect-web

Length of output: 712


🏁 Script executed:

# Verify the exact canonical construction to see if double slash would actually occur
head -n 35 'src/app/(home)/page.tsx' | tail -n +20

Repository: solid-connection/solid-connect-web

Length of output: 672


baseUrl 설정 시 후행 슬래시로 인한 canonical/OG URL 중복 방지

환경 변수로 전달되는 NEXT_PUBLIC_WEB_URL이 후행 슬래시로 끝나는 경우를 대비해야 합니다:

  1. 후행 슬래시 제거
    ${baseUrl}/ 패턴에서 NEXT_PUBLIC_WEB_URL="https://example.com/"이면 https://example.com//이 되어 URL 정규화를 깨뜨립니다.

  2. 영향 범위

    • Line 28: canonical URL
    • Line 34: openGraph.url
    • Line 60: JSON-LD url
    • Line 21: ogImageUrl
      모두 동일한 위험을 가집니다.
-const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com";
+const baseUrl = (process.env.NEXT_PUBLIC_WEB_URL ?? "https://solid-connection.com").replace(/\/+$/, "");
 const ogImageUrl = `${baseUrl}/opengraph-image.png`;

참고: authUtils.ts에서도 직접 process.env.NEXT_PUBLIC_WEB_URL을 사용하므로 동일한 처리가 필요합니다.

📝 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.

Suggested change
const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com";
const ogImageUrl = `${baseUrl}/opengraph-image.png`;
const baseUrl = (process.env.NEXT_PUBLIC_WEB_URL ?? "https://solid-connection.com").replace(/\/+$/, "");
const ogImageUrl = `${baseUrl}/opengraph-image.png`;
🤖 Prompt for AI Agents
In src/app/(home)/page.tsx around lines 20 to 22, baseUrl is taken directly from
NEXT_PUBLIC_WEB_URL which can end with a trailing slash and cause double-slashes
in constructed URLs (canonical, openGraph.url, JSON-LD url, ogImageUrl); trim
any trailing slash from the environment value before using it (e.g., normalize
process.env.NEXT_PUBLIC_WEB_URL by removing a trailing '/' unless the value is
just '/' or empty) and use that normalized baseUrl to build
canonical/OG/JSON-LD/ogImageUrl; apply the same normalization in authUtils.ts
where NEXT_PUBLIC_WEB_URL is used.

Comment on lines +77 to +79
<>
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
<div className="w-full">
Copy link

@coderabbitai coderabbitai bot Dec 12, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

4) dangerouslySetInnerHTML는 린트 에러 + 잠재 XSS 포인트라 교체가 필요해요.

  • 현재는 상수 기반이라 “즉시 취약”까지는 아닐 수 있지만, 린트가 에러로 잡고 있어 머지 블로커에 가까워요.
  • next/script로 바꾸면 의도도 명확해지고 규칙도 피할 수 있어요(그리고 < escaping도 같이 해두면 더 단단해요).
 import { Metadata } from "next";
 import dynamic from "next/dynamic";
 import Link from "next/link";
+import Script from "next/script";
@@
 const structuredData = {
   "@context": "https://schema.org",
   "@type": "WebSite",
@@
 };
+
+const structuredDataJson = JSON.stringify(structuredData).replace(/</g, "\\u003c");
@@
   return (
     <>
-      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} />
+      <Script id="home-structured-data" type="application/ld+json" strategy="beforeInteractive">
+        {structuredDataJson}
+      </Script>
       <div className="w-full">

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 77-77: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
In src/app/(home)/page.tsx around lines 77 to 79, replace the raw <script
dangerouslySetInnerHTML> with Next's Script component and ensure the JSON-LD
string has '<' escaped to avoid XSS lint errors; import Script from
'next/script', then render <Script id="structured-data"
type="application/ld+json" dangerouslySetInnerHTML={{ __html:
JSON.stringify(structuredData).replace(/</g, '\\u003c') }} /> so the linter no
longer flags the pattern and the payload is safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rsc라서 서버 작업이고 상수인데 xss 공격 위험이 있습니까?

서버데이터는 안전하게 보관되고 있다는 가정하입니다

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

Next.js 15 JSON-LD structured data best practices Script component

💡 Result:

Here are concise Next.js 15 best practices for adding JSON‑LD (structured data) using script tags:

  1. Render JSON‑LD as a <script type="application/ld+json"> inside your page or layout (server component) — Next.js recommends this. [1][2]

  2. Sanitize/escape before inlining to avoid XSS — e.g. JSON.stringify(jsonLd).replace(/</g, '\u003c') or use a safe serializer (serialize-javascript) per your security policy. Example:

    <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/. [1]
  3. Placement:

    • Put JSON‑LD in the layout/page that matches the content (head or top of page) so server-rendered HTML includes it for crawlers. Avoid duplicating the same schema across layouts/pages. [1][2]
  4. When to use next/script:

    • Prefer plain <script type="application/ld+json"> for static/server-known JSON‑LD.
    • If you must inject dynamic JSON‑LD client-side, use next/script with strategy="beforeInteractive" (or push to dataLayer) to ensure it appears correctly and avoid hydration issues — but use this only when JSON‑LD cannot be produced server-side. [3]
  5. Content correctness & scope:

    • Use the most specific Schema.org type, keep values accurate and visible on the page, include required/recommended properties, and don’t mark up content that isn’t present (avoids search penalties). [4]
  6. Types & tooling:

    • Use TypeScript typing libs (e.g., schema-dts) to model JSON‑LD objects and validate during dev. Validate final output with Google Rich Results Test / Schema Markup Validator and monitor Search Console. [1][4]
  7. Avoid common pitfalls:

    • Duplicate scripts (check layouts and components), invalid JSON syntax, mismatched server/client JSON (causes hydration diffs), and leaking unsanitized user content into JSON‑LD. [1][4]

Sources:

  • Next.js JSON‑LD guide and metadata docs. [1][2]
  • Community guidance on using next/script for JSON‑LD (strategy beforeInteractive). [3]
  • JSON‑LD best practices / validation guidance. [4]

@manNomi 맞습니다. 귀하의 평가가 정확해요.

1. RSC + 상수 데이터 → XSS 위험 거의 없음

  • 서버 컴포넌트에서 하드코딩된 상수만 사용하고 있고, 사용자 입력이 전혀 들어가지 않기 때문에 실질적인 XSS 공격 벡터가 없습니다.
  • baseUrl도 환경 변수(빌드 타임)라서 안전합니다.

2. 린트 경고의 성격

  • 린트 도구는 패턴 기반으로 dangerouslySetInnerHTML을 발견하면 맥락을 파악하지 못하고 무조건 경고를 띄웁니다.
  • 실제 위험이 아니라 "잠재적으로 위험할 수 있는 패턴"을 감지한 것입니다.

3. Next.js 권장사항

  • 웹 검색 결과에 따르면, Next.js는 정적 JSON-LD의 경우 RSC에서 일반 <script type="application/ld+json">을 사용하는 것을 권장합니다.
  • 다만 방어적 차원에서 JSON.stringify(structuredData).replace(/</g, '\\u003c') 같은 이스케이핑을 "best practice"로 제안하고 있지만, 현재 코드처럼 상수만 사용하는 경우엔 필수는 아닙니다.

현재 코드가 안전하다고 판단하신다면 그대로 유지하셔도 괜찮습니다. 린트 경고를 해소하고 싶으시면 간단한 < 이스케이핑만 추가하시면 됩니다. 선택은 귀하께 맡기겠습니다! 😊


🧠 Learnings used
Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 242
File: src/components/ui/TabSelector.tsx:10-11
Timestamp: 2025-08-12T09:41:44.182Z
Learning: manNomi prefers to keep reusable UI components simple and focused on core functionality rather than adding many features. They don't want to over-engineer flexible/reusable UI components at the initial stage.

Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/components/mentor/MentorChatCard/index.tsx:17-21
Timestamp: 2025-08-24T11:14:34.297Z
Learning: manNomi prefers not to receive accessibility suggestions or recommendations during code reviews.

Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/api/auth/client/usePostLogout.ts:17-33
Timestamp: 2025-08-24T11:13:08.477Z
Learning: manNomi prefers to prioritize user experience over perfect state consistency in auth flows. Specifically, in logout scenarios, they prefer to keep tokens intact on API failure to avoid forcing users to re-login, even if it means temporary UI state inconsistency.

Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 245
File: src/api/auth/client/useDeleteUserAccount.ts:17-27
Timestamp: 2025-08-24T11:11:40.758Z
Learning: manNomi prefers optimistic navigation in auth-related flows (like account deletion) to prevent race conditions where token clearing before navigation could cause intermediate redirects to login page due to pending async requests failing.

Learnt from: manNomi
Repo: solid-connection/solid-connect-web PR: 242
File: src/types/mentor.ts:70-73
Timestamp: 2025-08-12T04:07:04.134Z
Learning: manNomi prefers using Korean labels directly in enum values (e.g., MentorTab.MY_MENTEE = "나의 멘티") over separating enum keys from UI labels for efficiency and intuitiveness, considering scalability concerns as excessive for their current project needs.

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