-
Notifications
You must be signed in to change notification settings - Fork 2
feat : 홈페이지 스트럭처 데이터 추가 #312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,11 +17,52 @@ const NewsSectionDynamic = dynamic(() => import("./_ui/NewsSection"), { | |
| loading: () => <NewsSectionSkeleton />, | ||
| }); | ||
|
|
||
| const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com"; | ||
| const ogImageUrl = `${baseUrl}/opengraph-image.png`; | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: "솔리드 커넥션 – 교환학생의 첫 걸음", | ||
| description: "교환학생 준비를 위한 모든 정보가 여기에!", | ||
| title: "교환학생 사이트 | 솔리드 커넥션 – 교환학생 커뮤니티", | ||
| description: | ||
| "교환학생 사이트 솔리드커넥션. 교환학생 커뮤니티에서 학교 검색, 성적 입력, 지원 현황 확인까지 한 번에. 교환학생 준비를 위한 모든 정보를 제공합니다.", | ||
| alternates: { | ||
| canonical: "https://solid-connection.com/", | ||
| canonical: `${baseUrl}/`, | ||
| }, | ||
| openGraph: { | ||
| title: "교환학생 사이트 | 솔리드 커넥션 – 교환학생 커뮤니티", | ||
| description: | ||
| "교환학생 사이트 솔리드커넥션. 교환학생 커뮤니티에서 학교 검색, 성적 입력, 지원 현황 확인까지 한 번에. 교환학생 준비를 위한 모든 정보를 제공합니다.", | ||
| url: `${baseUrl}/`, | ||
| siteName: "솔리드커넥션", | ||
| locale: "ko_KR", | ||
| type: "website", | ||
| images: [ | ||
| { | ||
| url: ogImageUrl, | ||
| width: 1200, | ||
| height: 630, | ||
| alt: "솔리드 커넥션 - 교환학생 커뮤니티", | ||
| }, | ||
| ], | ||
| }, | ||
| twitter: { | ||
| card: "summary_large_image", | ||
| title: "교환학생 사이트 | 솔리드 커넥션 – 교환학생 커뮤니티", | ||
| description: "교환학생 사이트 솔리드커넥션. 교환학생 커뮤니티에서 학교 검색, 성적 입력, 지원 현황 확인까지.", | ||
| images: [ogImageUrl], | ||
| }, | ||
| }; | ||
|
|
||
| // Structured Data (JSON-LD) for SEO - 교환학생 사이트, 커뮤니티 키워드 강화 | ||
| const structuredData = { | ||
| "@context": "https://schema.org", | ||
| "@type": "WebSite", | ||
| name: "솔리드커넥션", | ||
| url: `${baseUrl}/`, | ||
| description: "교환학생 학교 검색, 성적 입력, 지원 현황 확인까지 가능한 교환학생 플랫폼.", | ||
| potentialAction: { | ||
| "@type": "SearchAction", | ||
| target: `${baseUrl}/university?searchText={search_term_string}`, | ||
| "query-input": "required name=search_term_string", | ||
| }, | ||
| }; | ||
|
|
||
|
|
@@ -33,71 +74,77 @@ const HomePage = async () => { | |
| const allRegionsUniversityList = await getCategorizedUniversities(); | ||
|
|
||
| return ( | ||
| <div className="w-full"> | ||
| <FindLastYearScoreBar /> | ||
| <div className="flex flex-col gap-2.5 px-5 py-3.5"> | ||
| <div className="flex gap-2"> | ||
| <Link | ||
| className="h-26 bg-bg-accent-blue flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/search" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-secondary">학교 검색하기</span> | ||
| <span className="typo-medium-4 text-k-700">모든 학교 목록을 확인해보세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconMagnifyingGlass /> | ||
| </div> | ||
| </Link> | ||
| <Link className="h-26 bg-bg-accent-sky flex flex-1 flex-col gap-2 rounded-lg p-2.5" href="/university/score"> | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-sub-a">성적 입력하기</span> | ||
| <span className="typo-medium-4 text-k-700">성적을 입력해보세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconPaper /> | ||
| </div> | ||
| </Link> | ||
| <> | ||
| <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} /> | ||
| <div className="w-full"> | ||
|
Comment on lines
+77
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 4)
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">
🧰 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. (react-unsafe-html-injection) 🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rsc라서 서버 작업이고 상수인데 xss 공격 위험이 있습니까? 서버데이터는 안전하게 보관되고 있다는 가정하입니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query: 💡 Result: Here are concise Next.js 15 best practices for adding JSON‑LD (structured data) using script tags:
Sources:
@manNomi 맞습니다. 귀하의 평가가 정확해요. 1. RSC + 상수 데이터 → XSS 위험 거의 없음
2. 린트 경고의 성격
3. Next.js 권장사항
현재 코드가 안전하다고 판단하신다면 그대로 유지하셔도 괜찮습니다. 린트 경고를 해소하고 싶으시면 간단한 🧠 Learnings used |
||
| <FindLastYearScoreBar /> | ||
| <div className="flex flex-col gap-2.5 px-5 py-3.5"> | ||
| <div className="flex gap-2"> | ||
| <Link | ||
| className="h-26 bg-bg-accent-blue flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/search" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-secondary">학교 검색하기</span> | ||
| <span className="typo-medium-4 text-k-700">모든 학교 목록을 확인해보세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconMagnifyingGlass /> | ||
| </div> | ||
| </Link> | ||
| <Link | ||
| className="h-26 bg-bg-accent-sky flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/score" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-sub-a">성적 입력하기</span> | ||
| <span className="typo-medium-4 text-k-700">성적을 입력해보세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconPaper /> | ||
| </div> | ||
| </Link> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Link | ||
| className="h-26 bg-bg-accent-orange flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/application/apply" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-accent-custom-orange">학교 지원하기</span> | ||
| <span className="typo-medium-4 text-k-700">학교를 지원해주세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconMuseum /> | ||
| </div> | ||
| </Link> | ||
| <Link | ||
| className="h-26 bg-bg-accent-green flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/application" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-accent-custom-green">지원자 현황 확인</span> | ||
| <span className="typo-medium-4 text-k-700">경쟁률을 바로 분석해드려요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconIdCard /> | ||
| </div> | ||
| </Link> | ||
| </div> | ||
| </div> | ||
| <div className="flex gap-2"> | ||
| <Link | ||
| className="h-26 bg-bg-accent-orange flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/application/apply" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-accent-custom-orange">학교 지원하기</span> | ||
| <span className="typo-medium-4 text-k-700">학교를 지원해주세요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconMuseum /> | ||
| </div> | ||
| </Link> | ||
| <Link | ||
| className="h-26 bg-bg-accent-green flex flex-1 flex-col gap-2 rounded-lg p-2.5" | ||
| href="/university/application" | ||
| > | ||
| <div className="flex flex-col"> | ||
| <span className="typo-bold-5 text-accent-custom-green">지원자 현황 확인</span> | ||
| <span className="typo-medium-4 text-k-700">경쟁률을 바로 분석해드려요</span> | ||
| </div> | ||
| <div className="flex justify-end"> | ||
| <IconIdCard /> | ||
| </div> | ||
| </Link> | ||
|
|
||
| <div className="border-t-[5px] border-k-50 py-5 pl-5"> | ||
| <div className="typo-sb-7 mb-2 flex items-center gap-1.5 font-serif text-k-700">실시간 인기있는 파견학교</div> | ||
| <PopularUniversitySection universities={recommendedUniversities} /> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="border-t-[5px] border-k-50 py-5 pl-5"> | ||
| <div className="typo-sb-7 mb-2 flex items-center gap-1.5 font-serif text-k-700">실시간 인기있는 파견학교</div> | ||
| <PopularUniversitySection universities={recommendedUniversities} /> | ||
| </div> | ||
| <div className="p-5"> | ||
| <UniversityList allRegionsUniversityList={allRegionsUniversityList} /> | ||
| </div> | ||
|
|
||
| <div className="p-5"> | ||
| <UniversityList allRegionsUniversityList={allRegionsUniversityList} /> | ||
| <NewsSectionDynamic newsList={newsList} /> | ||
| </div> | ||
|
|
||
| <NewsSectionDynamic newsList={newsList} /> | ||
| </div> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 213
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 105
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 60
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 724
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 376
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 256
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 984
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 60
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 233
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 712
🏁 Script executed:
Repository: solid-connection/solid-connect-web
Length of output: 672
baseUrl 설정 시 후행 슬래시로 인한 canonical/OG URL 중복 방지
환경 변수로 전달되는
NEXT_PUBLIC_WEB_URL이 후행 슬래시로 끝나는 경우를 대비해야 합니다:후행 슬래시 제거
${baseUrl}/패턴에서NEXT_PUBLIC_WEB_URL="https://example.com/"이면https://example.com//이 되어 URL 정규화를 깨뜨립니다.영향 범위
모두 동일한 위험을 가집니다.
참고:
authUtils.ts에서도 직접process.env.NEXT_PUBLIC_WEB_URL을 사용하므로 동일한 처리가 필요합니다.📝 Committable suggestion
🤖 Prompt for AI Agents