From 9a527d76933a4271f19f18291fc89aa1c5637aa8 Mon Sep 17 00:00:00 2001 From: manNomi Date: Mon, 1 Jun 2026 16:13:38 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EA=B2=80=EC=83=89=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94=20=EA=B8=B0=EB=B0=98=20=EC=A0=95=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/(home)/page.tsx | 16 ++-- apps/web/src/app/api/revalidate/route.ts | 4 +- .../[boardCode]/[postId]/modify/page.tsx | 2 + .../community/[boardCode]/[postId]/page.tsx | 4 +- .../app/community/[boardCode]/create/page.tsx | 2 + .../src/app/community/[boardCode]/page.tsx | 24 +++++- apps/web/src/app/community/page.tsx | 3 + apps/web/src/app/layout.tsx | 3 +- .../web/src/app/login/apple/callback/page.tsx | 2 + .../web/src/app/login/kakao/callback/page.tsx | 2 + apps/web/src/app/login/page.tsx | 2 + apps/web/src/app/mentor/[id]/page.tsx | 5 +- .../web/src/app/mentor/chat/[chatId]/page.tsx | 5 +- apps/web/src/app/mentor/chat/page.tsx | 5 +- apps/web/src/app/mentor/modify/page.tsx | 5 +- apps/web/src/app/mentor/page.tsx | 10 ++- apps/web/src/app/mentor/waiting/page.tsx | 5 +- apps/web/src/app/my/apply-mentor/layout.tsx | 13 +++ apps/web/src/app/my/favorite/page.tsx | 2 + apps/web/src/app/my/match/page.tsx | 2 + apps/web/src/app/my/modify/page.tsx | 2 + apps/web/src/app/my/page.tsx | 2 + apps/web/src/app/my/password/page.tsx | 2 + apps/web/src/app/robots.ts | 13 +-- apps/web/src/app/sign-up/email/page.tsx | 2 + apps/web/src/app/sign-up/page.tsx | 2 + apps/web/src/app/sitemap.ts | 83 ++++++++++++------- apps/web/src/app/terms/page.tsx | 4 + apps/web/src/app/university/(home)/page.tsx | 20 +++++ .../university/[homeUniversity]/[id]/page.tsx | 28 +++---- .../app/university/[homeUniversity]/page.tsx | 29 ++++++- .../[homeUniversity]/search/page.tsx | 14 +++- .../app/university/application/apply/page.tsx | 2 + .../src/app/university/application/page.tsx | 2 + .../list/[homeUniversityName]/page.tsx | 3 + .../app/university/score/example/layout.tsx | 13 +++ apps/web/src/app/university/score/page.tsx | 2 + .../app/university/score/submit/gpa/page.tsx | 2 + .../score/submit/language-test/page.tsx | 2 + apps/web/src/app/university/search/page.tsx | 2 + apps/web/src/utils/seo.ts | 66 +++++++++++++++ 41 files changed, 317 insertions(+), 94 deletions(-) create mode 100644 apps/web/src/app/my/apply-mentor/layout.tsx create mode 100644 apps/web/src/app/university/score/example/layout.tsx create mode 100644 apps/web/src/utils/seo.ts diff --git a/apps/web/src/app/(home)/page.tsx b/apps/web/src/app/(home)/page.tsx index d26b24c0..0b4c5bb0 100644 --- a/apps/web/src/app/(home)/page.tsx +++ b/apps/web/src/app/(home)/page.tsx @@ -2,14 +2,15 @@ import type { Metadata } from "next"; import { getHomeNewsList } from "@/apis/news/server/getNewsList"; import { getCategorizedUniversities, getRecommendedUniversity } from "@/apis/universities/server"; import { type ListUniversity, RegionEnumExtend } from "@/types/university"; +import { createUrl } from "@/utils/seo"; import FindLastYearScoreBar from "./_ui/FindLastYearScoreBar"; import HomeEntrySection from "./_ui/HomeEntrySection"; import NewsSection from "./_ui/NewsSection"; import PopularUniversitySection from "./_ui/PopularUniversitySection"; import UniversityList from "./_ui/UniversityList"; -const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com"; -const ogImageUrl = `${baseUrl}/opengraph-image.png`; +const pageUrl = createUrl("/"); +const ogImageUrl = createUrl("/opengraph-image.png"); const homeMetaTitle = "교환학생 사이트 | 솔리드 커넥션 – 교환학생 커뮤니티, 플랫폼"; export const metadata: Metadata = { @@ -17,13 +18,13 @@ export const metadata: Metadata = { description: "교환학생 사이트 솔리드커넥션. 교환학생 커뮤니티에서 학교 검색, 성적 입력, 지원 현황 확인까지 한 번에. 교환학생 준비를 위한 모든 정보를 제공합니다.", alternates: { - canonical: `${baseUrl}/`, + canonical: pageUrl, }, openGraph: { title: homeMetaTitle, description: "교환학생 사이트 솔리드커넥션. 교환학생 커뮤니티에서 학교 검색, 성적 입력, 지원 현황 확인까지 한 번에. 교환학생 준비를 위한 모든 정보를 제공합니다.", - url: `${baseUrl}/`, + url: pageUrl, siteName: "솔리드커넥션", locale: "ko_KR", type: "website", @@ -49,13 +50,8 @@ const structuredData = { "@context": "https://schema.org", "@type": "WebSite", name: "솔리드커넥션", - url: `${baseUrl}/`, + url: pageUrl, description: "교환학생 학교 검색, 성적 입력, 지원 현황 확인까지 가능한 교환학생 플랫폼.", - potentialAction: { - "@type": "SearchAction", - target: `${baseUrl}/university?searchText={search_term_string}`, - "query-input": "required name=search_term_string", - }, }; const resolveRecommendedUniversitiesHomeUniversityName = ( diff --git a/apps/web/src/app/api/revalidate/route.ts b/apps/web/src/app/api/revalidate/route.ts index 7cdb7bc8..11a1568d 100644 --- a/apps/web/src/app/api/revalidate/route.ts +++ b/apps/web/src/app/api/revalidate/route.ts @@ -57,7 +57,7 @@ async function POST(request: NextRequest) { // boardCode가 있으면 해당 커뮤니티 페이지 revalidate if (boardCode) { revalidatePath(`/community/${boardCode}`); - revalidateTag(`posts-${boardCode}`, { expire: 0 }); + revalidateTag(`posts-${boardCode}`); return NextResponse.json({ revalidated: true, @@ -78,7 +78,7 @@ async function POST(request: NextRequest) { // 특정 태그 revalidate if (tag) { - revalidateTag(tag, { expire: 0 }); + revalidateTag(tag); return NextResponse.json({ revalidated: true, message: `Tag ${tag} revalidated`, diff --git a/apps/web/src/app/community/[boardCode]/[postId]/modify/page.tsx b/apps/web/src/app/community/[boardCode]/[postId]/modify/page.tsx index ca1b315f..386875a1 100644 --- a/apps/web/src/app/community/[boardCode]/[postId]/modify/page.tsx +++ b/apps/web/src/app/community/[boardCode]/[postId]/modify/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import PostModifyContent from "./PostModifyContent"; @@ -13,6 +14,7 @@ interface PostModifyPageProps { export const metadata: Metadata = { title: "글 수정", + robots: NO_INDEX_ROBOTS, }; const PostModifyPage = async ({ params }: PostModifyPageProps) => { diff --git a/apps/web/src/app/community/[boardCode]/[postId]/page.tsx b/apps/web/src/app/community/[boardCode]/[postId]/page.tsx index cd382117..a2e65a6f 100644 --- a/apps/web/src/app/community/[boardCode]/[postId]/page.tsx +++ b/apps/web/src/app/community/[boardCode]/[postId]/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import PostPageContent from "./PostPageContent"; interface PostPageProps { @@ -10,7 +11,8 @@ interface PostPageProps { } export const metadata: Metadata = { - title: "게시글", + title: "게시글 | 솔리드커넥션", + robots: NO_INDEX_ROBOTS, }; const PostPage = async ({ params }: PostPageProps) => { diff --git a/apps/web/src/app/community/[boardCode]/create/page.tsx b/apps/web/src/app/community/[boardCode]/create/page.tsx index a95ec635..0659d4f2 100644 --- a/apps/web/src/app/community/[boardCode]/create/page.tsx +++ b/apps/web/src/app/community/[boardCode]/create/page.tsx @@ -1,7 +1,9 @@ +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import PostForm from "./PostForm"; export const metadata = { title: "글쓰기", + robots: NO_INDEX_ROBOTS, }; const PostCreatePage = async ({ params }: { params: Promise<{ boardCode: string }> }) => { diff --git a/apps/web/src/app/community/[boardCode]/page.tsx b/apps/web/src/app/community/[boardCode]/page.tsx index a61b97dc..03146f0c 100644 --- a/apps/web/src/app/community/[boardCode]/page.tsx +++ b/apps/web/src/app/community/[boardCode]/page.tsx @@ -1,17 +1,33 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { COMMUNITY_BOARDS } from "@/constants/community"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import CommunityPageContent from "./CommunityPageContent"; -export const metadata: Metadata = { - title: "커뮤니티", -}; - interface CommunityPageProps { params: Promise<{ boardCode: string; }>; } +export async function generateMetadata({ params }: CommunityPageProps): Promise { + const { boardCode } = await params; + const board = COMMUNITY_BOARDS.find((item) => item.code === boardCode); + + if (!board) { + return { + title: "커뮤니티", + robots: NO_INDEX_ROBOTS, + }; + } + + return { + title: `${board.nameKo} 교환학생 커뮤니티 | 솔리드커넥션`, + description: `${board.nameKo} 교환학생 준비, 파견 생활, 학교 정보와 질문을 나누는 솔리드커넥션 커뮤니티입니다.`, + robots: NO_INDEX_ROBOTS, + }; +} + const CommunityPage = async ({ params }: CommunityPageProps) => { const { boardCode } = await params; diff --git a/apps/web/src/app/community/page.tsx b/apps/web/src/app/community/page.tsx index b3f0d56e..8a8ef218 100644 --- a/apps/web/src/app/community/page.tsx +++ b/apps/web/src/app/community/page.tsx @@ -1,8 +1,11 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; + export const metadata: Metadata = { title: "커뮤니티", + robots: NO_INDEX_ROBOTS, }; const CommunityIndex = () => { diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 33b8d0e2..c4e8bd18 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -10,8 +10,9 @@ import AppleScriptLoader from "@/lib/ScriptLoader/AppleScriptLoader"; import "@/styles/globals.css"; import { GoogleAnalytics } from "@next/third-parties/google"; import { SpeedInsights } from "@vercel/speed-insights/next"; +import { getSiteUrl } from "@/utils/seo"; -const siteUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com"; +const siteUrl = getSiteUrl(); export const metadata: Metadata = { metadataBase: new URL(siteUrl), diff --git a/apps/web/src/app/login/apple/callback/page.tsx b/apps/web/src/app/login/apple/callback/page.tsx index 1eb1a037..448fb8ee 100644 --- a/apps/web/src/app/login/apple/callback/page.tsx +++ b/apps/web/src/app/login/apple/callback/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import { Suspense } from "react"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import AppleLoginCallbackPage from "./AppleLoginCallbackPage"; export const metadata: Metadata = { title: "애플 로그인 중...", + robots: NO_INDEX_ROBOTS, }; const Page = () => ( diff --git a/apps/web/src/app/login/kakao/callback/page.tsx b/apps/web/src/app/login/kakao/callback/page.tsx index 4a8a9d2e..d56e8325 100644 --- a/apps/web/src/app/login/kakao/callback/page.tsx +++ b/apps/web/src/app/login/kakao/callback/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import { Suspense } from "react"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import KakaoLoginCallbackPage from "./KakaoLoginCallbackPage"; export const metadata: Metadata = { title: "카카오 로그인 중...", + robots: NO_INDEX_ROBOTS, }; const Page = () => ( diff --git a/apps/web/src/app/login/page.tsx b/apps/web/src/app/login/page.tsx index 4346dbfd..6c04cca5 100644 --- a/apps/web/src/app/login/page.tsx +++ b/apps/web/src/app/login/page.tsx @@ -1,9 +1,11 @@ import type { Metadata } from "next"; import KakaoScriptLoader from "@/lib/ScriptLoader/KakaoScriptLoader"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import LoginContent from "./LoginContent"; export const metadata: Metadata = { title: "로그인", + robots: NO_INDEX_ROBOTS, }; const LoginPage = () => { diff --git a/apps/web/src/app/mentor/[id]/page.tsx b/apps/web/src/app/mentor/[id]/page.tsx index 0b34a435..23788c73 100644 --- a/apps/web/src/app/mentor/[id]/page.tsx +++ b/apps/web/src/app/mentor/[id]/page.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import MentorDetialContent from "./_ui/MentorDetialContent"; export const metadata: Metadata = { - title: "멘토 상세 정보 | Solid Connect", + title: "멘토 상세 정보 | 솔리드커넥션", description: "멘토의 상세 정보와 경험, 아티클을 확인하고 멘토링을 신청해보세요.", - keywords: ["멘토", "멘토링", "유학", "상세정보", "교환학생"], + robots: NO_INDEX_ROBOTS, }; interface MentorDetailPageProps { diff --git a/apps/web/src/app/mentor/chat/[chatId]/page.tsx b/apps/web/src/app/mentor/chat/[chatId]/page.tsx index f2596a4c..4e5dc3e6 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/page.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/page.tsx @@ -1,13 +1,14 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ChatContent from "./_ui/ChatContent"; import ChatNavBar from "./_ui/ChatNavBar"; export const metadata: Metadata = { - title: "멘토와 채팅 | Solid Connect", + title: "멘토와 채팅 | 솔리드커넥션", description: "멘토와 실시간으로 대화하며 궁금한 점을 해결해보세요.", - keywords: ["멘토", "채팅", "실시간", "대화", "멘토링", "질문"], + robots: NO_INDEX_ROBOTS, }; interface ChatDetailPageProps { diff --git a/apps/web/src/app/mentor/chat/page.tsx b/apps/web/src/app/mentor/chat/page.tsx index 2640e5c3..eab30e15 100644 --- a/apps/web/src/app/mentor/chat/page.tsx +++ b/apps/web/src/app/mentor/chat/page.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ChatPageClient from "./_ui/ChatPageClient"; export const metadata: Metadata = { - title: "멘토 채팅 목록 | Solid Connect", + title: "멘토 채팅 목록 | 솔리드커넥션", description: "멘토와의 채팅 목록을 확인하고 대화를 이어가세요.", - keywords: ["멘토", "채팅", "대화", "멘토링", "소통"], + robots: NO_INDEX_ROBOTS, }; const ChatPage = () => { diff --git a/apps/web/src/app/mentor/modify/page.tsx b/apps/web/src/app/mentor/modify/page.tsx index a8970384..7a4e7169 100644 --- a/apps/web/src/app/mentor/modify/page.tsx +++ b/apps/web/src/app/mentor/modify/page.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ModifyContent from "./_ui/ModifyContent"; export const metadata: Metadata = { - title: "멘토 정보 수정 | Solid Connect", + title: "멘토 정보 수정 | 솔리드커넥션", description: "멘토 프로필과 정보를 수정하여 더 나은 멘토링을 제공하세요.", - keywords: ["멘토", "정보수정", "프로필", "멘토링", "설정"], + robots: NO_INDEX_ROBOTS, }; const ModifyPage = () => { diff --git a/apps/web/src/app/mentor/page.tsx b/apps/web/src/app/mentor/page.tsx index 982de783..1c29c649 100644 --- a/apps/web/src/app/mentor/page.tsx +++ b/apps/web/src/app/mentor/page.tsx @@ -1,11 +1,15 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import MentorClient from "./_ui/MentorClient"; +const title = "교환학생 멘토 찾기 | 솔리드커넥션"; +const description = "교환학생을 다녀온 멘토의 국가, 파견 대학, 합격 팁을 확인하고 나에게 맞는 멘토링을 신청해보세요."; + export const metadata: Metadata = { - title: "멘토 찾기 | Solid Connect", - description: "다양한 분야의 전문 멘토들을 만나보세요. 유학, 취업, 진로 상담을 받을 수 있습니다.", - keywords: ["멘토", "멘토링", "유학", "취업", "진로상담", "전문가"], + title, + description, + robots: NO_INDEX_ROBOTS, }; const MentorPage = () => { diff --git a/apps/web/src/app/mentor/waiting/page.tsx b/apps/web/src/app/mentor/waiting/page.tsx index c75cb13f..051e0472 100644 --- a/apps/web/src/app/mentor/waiting/page.tsx +++ b/apps/web/src/app/mentor/waiting/page.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import WaitingContent from "./_ui/WaitingContent"; export const metadata: Metadata = { - title: "멘토 승인 대기 | Solid Connect", + title: "멘토 승인 대기 | 솔리드커넥션", description: "멘토 신청이 접수되었습니다. 승인 결과를 기다려주세요.", - keywords: ["멘토", "승인", "대기", "신청", "멘토링"], + robots: NO_INDEX_ROBOTS, }; const WaitingPage = () => { diff --git a/apps/web/src/app/my/apply-mentor/layout.tsx b/apps/web/src/app/my/apply-mentor/layout.tsx new file mode 100644 index 00000000..a926aaef --- /dev/null +++ b/apps/web/src/app/my/apply-mentor/layout.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +import { NO_INDEX_ROBOTS } from "@/utils/seo"; + +export const metadata: Metadata = { + title: "멘토 회원 전환", + robots: NO_INDEX_ROBOTS, +}; + +const ApplyMentorLayout = ({ children }: { children: ReactNode }) => children; + +export default ApplyMentorLayout; diff --git a/apps/web/src/app/my/favorite/page.tsx b/apps/web/src/app/my/favorite/page.tsx index cd26f8c2..e08a0b26 100644 --- a/apps/web/src/app/my/favorite/page.tsx +++ b/apps/web/src/app/my/favorite/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import FavoriteContent from "./_ui/FavoriteContent"; export const metadata: Metadata = { title: "관심학교", + robots: NO_INDEX_ROBOTS, }; const FavoritePage = () => { diff --git a/apps/web/src/app/my/match/page.tsx b/apps/web/src/app/my/match/page.tsx index d49e783a..25b7956d 100644 --- a/apps/web/src/app/my/match/page.tsx +++ b/apps/web/src/app/my/match/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import MatchContent from "./_ui/MatchContent"; export const metadata: Metadata = { title: "프로필 수정", + robots: NO_INDEX_ROBOTS, }; const MatchPage = () => { diff --git a/apps/web/src/app/my/modify/page.tsx b/apps/web/src/app/my/modify/page.tsx index 014c523f..3945ec6a 100644 --- a/apps/web/src/app/my/modify/page.tsx +++ b/apps/web/src/app/my/modify/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ModifyContent from "./_ui/ModifyContent"; export const metadata: Metadata = { title: "프로필 수정", + robots: NO_INDEX_ROBOTS, }; const ModifyPage = () => { diff --git a/apps/web/src/app/my/page.tsx b/apps/web/src/app/my/page.tsx index 9652e8a4..b29490b0 100644 --- a/apps/web/src/app/my/page.tsx +++ b/apps/web/src/app/my/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import MyProfileContent from "./_ui/MyProfileContent"; export const metadata: Metadata = { title: "마이페이지", + robots: NO_INDEX_ROBOTS, }; const MyPage = () => { diff --git a/apps/web/src/app/my/password/page.tsx b/apps/web/src/app/my/password/page.tsx index a6e485c4..a2490b2a 100644 --- a/apps/web/src/app/my/password/page.tsx +++ b/apps/web/src/app/my/password/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import PasswordContent from "./_ui/PasswordContent"; export const metadata: Metadata = { title: "비밀번호 수정", + robots: NO_INDEX_ROBOTS, }; const ModifyPage = () => { diff --git a/apps/web/src/app/robots.ts b/apps/web/src/app/robots.ts index a4c63744..6d831a0d 100644 --- a/apps/web/src/app/robots.ts +++ b/apps/web/src/app/robots.ts @@ -1,20 +1,11 @@ import type { MetadataRoute } from "next"; -const DEFAULT_SITE_URL = "https://www.solid-connection.com"; - -const getSiteUrl = () => (process.env.NEXT_PUBLIC_WEB_URL ?? DEFAULT_SITE_URL).replace(/\/$/, ""); +import { getSiteUrl, isNonIndexSiteUrl } from "@/utils/seo"; export default function robots(): MetadataRoute.Robots { const siteUrl = getSiteUrl(); - const vercelEnv = process.env.VERCEL_ENV; - const isNonIndexEnvironment = - vercelEnv === "preview" || - vercelEnv === "development" || - siteUrl.includes("stage") || - siteUrl.includes("localhost") || - siteUrl.includes("127.0.0.1"); - if (isNonIndexEnvironment) { + if (isNonIndexSiteUrl(siteUrl)) { return { rules: { userAgent: "*", diff --git a/apps/web/src/app/sign-up/email/page.tsx b/apps/web/src/app/sign-up/email/page.tsx index 32b3ba1d..6cf59550 100644 --- a/apps/web/src/app/sign-up/email/page.tsx +++ b/apps/web/src/app/sign-up/email/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import EmailSignUpForm from "./EmailSignUpForm"; export const metadata: Metadata = { title: "이메일로 시작하기", + robots: NO_INDEX_ROBOTS, }; const EmailSignUpPage = () => { diff --git a/apps/web/src/app/sign-up/page.tsx b/apps/web/src/app/sign-up/page.tsx index daf9ca81..3c9933af 100644 --- a/apps/web/src/app/sign-up/page.tsx +++ b/apps/web/src/app/sign-up/page.tsx @@ -3,9 +3,11 @@ import { Suspense } from "react"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import SignupSurvey from "@/components/login/signup/SignupSurvey"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; export const metadata: Metadata = { title: "회원가입", + robots: NO_INDEX_ROBOTS, }; const SignUpPage = () => { diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index 889fad81..067d6e39 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -1,42 +1,61 @@ import type { MetadataRoute } from "next"; +import { getAllUniversities } from "@/apis/universities/server"; +import { getHomeUniversitySlugByName, HOME_UNIVERSITY_LIST, HOME_UNIVERSITY_SLUGS } from "@/constants/university"; +import { createUrl, getSiteUrl, isNonIndexSiteUrl } from "@/utils/seo"; -const DEFAULT_SITE_URL = "https://www.solid-connection.com"; +type SitemapEntry = MetadataRoute.Sitemap[number]; -const getSiteUrl = () => (process.env.NEXT_PUBLIC_WEB_URL ?? DEFAULT_SITE_URL).replace(/\/$/, ""); +export const revalidate = 86400; -export default function sitemap(): MetadataRoute.Sitemap { +const toSitemapEntry = ( + path: string, + changeFrequency: SitemapEntry["changeFrequency"], + priority: number, + lastModified?: Date | string, +): SitemapEntry => ({ + url: createUrl(path), + ...(lastModified ? { lastModified } : {}), + changeFrequency, + priority, +}); + +const getStaticRoutes = (): MetadataRoute.Sitemap => [ + toSitemapEntry("/", "daily", 1), + toSitemapEntry("/university", "weekly", 0.85), + toSitemapEntry("/terms", "yearly", 0.35), + ...HOME_UNIVERSITY_LIST.map((university) => toSitemapEntry(`/university/${university.slug}`, "weekly", 0.8)), +]; + +const getUniversityDetailRoutes = async (): Promise => { + const universities = await getAllUniversities(); + const seenUrls = new Set(); + + return universities.flatMap((university) => { + const homeUniversitySlug = getHomeUniversitySlugByName(university.homeUniversityName); + + if (!homeUniversitySlug || !HOME_UNIVERSITY_SLUGS.includes(homeUniversitySlug)) { + return []; + } + + const entry = toSitemapEntry(`/university/${homeUniversitySlug}/${university.id}`, "monthly", 0.7); + + if (seenUrls.has(entry.url)) { + return []; + } + + seenUrls.add(entry.url); + return [entry]; + }); +}; + +export default async function sitemap(): Promise { const siteUrl = getSiteUrl(); - if (siteUrl.includes("stage") || siteUrl.includes("localhost") || siteUrl.includes("127.0.0.1")) { + if (isNonIndexSiteUrl(siteUrl)) { return []; } - const lastModified = new Date(); - - return [ - { - url: `${siteUrl}/`, - lastModified, - changeFrequency: "daily", - priority: 1, - }, - { - url: `${siteUrl}/mentor`, - lastModified, - changeFrequency: "weekly", - priority: 0.8, - }, - { - url: `${siteUrl}/university`, - lastModified, - changeFrequency: "weekly", - priority: 0.8, - }, - { - url: `${siteUrl}/terms`, - lastModified, - changeFrequency: "yearly", - priority: 0.5, - }, - ]; + const universityDetailRoutes = await getUniversityDetailRoutes(); + + return [...getStaticRoutes(), ...universityDetailRoutes]; } diff --git a/apps/web/src/app/terms/page.tsx b/apps/web/src/app/terms/page.tsx index 9f04077c..f8b99ed2 100644 --- a/apps/web/src/app/terms/page.tsx +++ b/apps/web/src/app/terms/page.tsx @@ -1,8 +1,12 @@ import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { createUrl } from "@/utils/seo"; export const metadata = { title: "이용약관", description: "솔리드커넥션 서비스 이용약관 페이지입니다.", + alternates: { + canonical: createUrl("/terms"), + }, }; const TERMS_SECTIONS = [ diff --git a/apps/web/src/app/university/(home)/page.tsx b/apps/web/src/app/university/(home)/page.tsx index 3b0fff91..9e37d4d4 100644 --- a/apps/web/src/app/university/(home)/page.tsx +++ b/apps/web/src/app/university/(home)/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { HOME_UNIVERSITY_LIST } from "@/constants/university"; +import { createUrl } from "@/utils/seo"; import HomeUniversityCard from "./_ui/HomeUniversityCard"; @@ -9,6 +10,25 @@ export const revalidate = 3600; // 1시간마다 재검증 (ISR) export const metadata: Metadata = { title: "대학 선택 | 솔리드커넥션", description: "소속 대학교를 선택하여 교환학생 정보를 확인하세요.", + alternates: { + canonical: createUrl("/university"), + }, + openGraph: { + title: "대학 선택 | 솔리드커넥션", + description: "소속 대학교를 선택하여 교환학생 정보를 확인하세요.", + url: createUrl("/university"), + siteName: "솔리드커넥션", + locale: "ko_KR", + type: "website", + images: [ + { + url: createUrl("/opengraph-image.png"), + width: 1200, + height: 630, + alt: "솔리드커넥션 대학 선택", + }, + ], + }, }; const UniversitySelectPage = () => { diff --git a/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx b/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx index cf3d8e3f..51924382 100644 --- a/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx +++ b/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx @@ -5,6 +5,7 @@ import { getAllUniversities, getUniversityDetail, getUniversityDetailWithStatus import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import { getHomeUniversityBySlug, HOME_UNIVERSITY_SLUGS, isMatchedHomeUniversityName } from "@/constants/university"; import type { HomeUniversitySlug } from "@/types/university"; +import { createAbsoluteUrl, createUrl, NO_INDEX_ROBOTS } from "@/utils/seo"; // UniversityDetail 컴포넌트 import UniversityDetail from "./_ui/UniversityDetail"; @@ -54,19 +55,17 @@ export async function generateMetadata({ params }: PageProps): Promise const universityData = await getUniversityDetail(Number(id)); if (!universityData) { - return { title: "파견 학교 상세" }; + return { + title: "파견 학교 상세", + robots: NO_INDEX_ROBOTS, + }; } const homeUniversityInfo = getHomeUniversityBySlug(homeUniversity); const convertedKoreanName = universityData.koreanName; - const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com"; - const pageUrl = `${baseUrl}/university/${homeUniversity}/${id}`; - const imageUrl = universityData.backgroundImageUrl - ? universityData.backgroundImageUrl.startsWith("http") - ? universityData.backgroundImageUrl - : `${baseUrl}${universityData.backgroundImageUrl}` - : `${baseUrl}/images/article-thumb.png`; + const pageUrl = createUrl(`/university/${homeUniversity}/${id}`); + const imageUrl = createAbsoluteUrl(universityData.backgroundImageUrl, "/images/article-thumb.png"); const countryExchangeKeyword = `${universityData.country} 교환학생`; const description = `${convertedKoreanName}(${universityData.englishName}) ${countryExchangeKeyword} 프로그램. 모집인원 ${universityData.studentCapacity}명. ${homeUniversityInfo?.shortName || ""} 학생을 위한 교환학생 정보.`; @@ -126,6 +125,10 @@ const CollegeDetailPage = async ({ params }: PageProps) => { if (!universityDetailResult.ok) { const isNotFoundError = universityDetailResult.status === 404; + if (isNotFoundError) { + notFound(); + } + return ( <> @@ -144,8 +147,7 @@ const CollegeDetailPage = async ({ params }: PageProps) => { const convertedKoreanName = universityData.koreanName; - const baseUrl = process.env.NEXT_PUBLIC_WEB_URL || "https://solid-connection.com"; - const pageUrl = `${baseUrl}/university/${homeUniversity}/${collegeId}`; + const pageUrl = createUrl(`/university/${homeUniversity}/${collegeId}`); const countryExchangeKeyword = `${universityData.country} 교환학생`; const structuredData = { @@ -155,11 +157,7 @@ const CollegeDetailPage = async ({ params }: PageProps) => { alternateName: universityData.englishName, url: pageUrl, description: `${convertedKoreanName}(${universityData.englishName}) ${countryExchangeKeyword} 프로그램 정보`, - image: universityData.backgroundImageUrl - ? universityData.backgroundImageUrl.startsWith("http") - ? universityData.backgroundImageUrl - : `${baseUrl}${universityData.backgroundImageUrl}` - : `${baseUrl}/images/article-thumb.png`, + image: createAbsoluteUrl(universityData.backgroundImageUrl, "/images/article-thumb.png"), }; return ( diff --git a/apps/web/src/app/university/[homeUniversity]/page.tsx b/apps/web/src/app/university/[homeUniversity]/page.tsx index a8985d77..5b027cfe 100644 --- a/apps/web/src/app/university/[homeUniversity]/page.tsx +++ b/apps/web/src/app/university/[homeUniversity]/page.tsx @@ -10,6 +10,7 @@ import { isMatchedHomeUniversityName, } from "@/constants/university"; import { type CountryCode, type HomeUniversitySlug, LanguageTestType, RegionEnumExtend } from "@/types/university"; +import { createUrl, NO_INDEX_ROBOTS } from "@/utils/seo"; import UniversityListContent from "./_ui/UniversityListContent"; @@ -69,12 +70,36 @@ export async function generateMetadata({ params }: PageProps): Promise if (!universityInfo) { return { title: "파견 학교 목록", + robots: NO_INDEX_ROBOTS, }; } + const title = `${universityInfo.shortName} 교환학생 파견 학교 목록 | 솔리드커넥션`; + const description = `${universityInfo.name} 학생들을 위한 교환학생 파견 학교 정보를 확인하세요. 국가, 권역, 어학 요건별로 지원 가능한 대학을 비교할 수 있습니다.`; + const pageUrl = createUrl(`/university/${homeUniversity}`); + return { - title: `${universityInfo.shortName} 교환학생 파견 학교 목록 | 솔리드커넥션`, - description: `${universityInfo.name} 학생들을 위한 교환학생 파견 학교 정보를 확인하세요.`, + title, + description, + alternates: { + canonical: pageUrl, + }, + openGraph: { + title, + description, + url: pageUrl, + siteName: "솔리드커넥션", + locale: "ko_KR", + type: "website", + images: [ + { + url: createUrl("/opengraph-image.png"), + width: 1200, + height: 630, + alt: `${universityInfo.shortName} 교환학생 파견 학교 목록`, + }, + ], + }, }; } diff --git a/apps/web/src/app/university/[homeUniversity]/search/page.tsx b/apps/web/src/app/university/[homeUniversity]/search/page.tsx index 960bc518..a5931948 100644 --- a/apps/web/src/app/university/[homeUniversity]/search/page.tsx +++ b/apps/web/src/app/university/[homeUniversity]/search/page.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import { getHomeUniversityBySlug, HOME_UNIVERSITY_SLUGS } from "@/constants/university"; import type { HomeUniversitySlug } from "@/types/university"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import SearchPageContent from "./_ui/SearchPageContent"; @@ -25,12 +26,19 @@ export async function generateMetadata({ params }: PageProps): Promise const universityInfo = getHomeUniversityBySlug(homeUniversity); if (!universityInfo) { - return { title: "파견 학교 검색" }; + return { + title: "파견 학교 검색", + robots: NO_INDEX_ROBOTS, + }; } + const title = `${universityInfo.shortName} 파견 학교 검색 | 솔리드커넥션`; + const description = `${universityInfo.name} 학생을 위한 맞춤 파견 학교를 검색하세요. 국가와 어학 조건을 기준으로 교환학생 지원 가능 대학을 찾아볼 수 있습니다.`; + return { - title: `${universityInfo.shortName} 파견 학교 검색 | 솔리드커넥션`, - description: `${universityInfo.name} 학생을 위한 맞춤 파견 학교를 검색하세요.`, + title, + description, + robots: NO_INDEX_ROBOTS, }; } diff --git a/apps/web/src/app/university/application/apply/page.tsx b/apps/web/src/app/university/application/apply/page.tsx index 83e31d3e..94d36a62 100644 --- a/apps/web/src/app/university/application/apply/page.tsx +++ b/apps/web/src/app/university/application/apply/page.tsx @@ -1,8 +1,10 @@ import type { Metadata } from "next"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ApplyPageContent from "./ApplyPageContent"; export const metadata: Metadata = { title: "지원하기", + robots: NO_INDEX_ROBOTS, }; const ApplyPage = () => { return ( diff --git a/apps/web/src/app/university/application/page.tsx b/apps/web/src/app/university/application/page.tsx index 54af993b..142d4976 100644 --- a/apps/web/src/app/university/application/page.tsx +++ b/apps/web/src/app/university/application/page.tsx @@ -1,11 +1,13 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ScorePageContent from "./ScorePageContent"; export const metadata: Metadata = { title: "점수 공유 현황", + robots: NO_INDEX_ROBOTS, }; const ScorePage = () => { diff --git a/apps/web/src/app/university/list/[homeUniversityName]/page.tsx b/apps/web/src/app/university/list/[homeUniversityName]/page.tsx index 8126e5ea..3f17ddac 100644 --- a/apps/web/src/app/university/list/[homeUniversityName]/page.tsx +++ b/apps/web/src/app/university/list/[homeUniversityName]/page.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; import { HOME_UNIVERSITY_LIST, HOME_UNIVERSITY_SLUG_MAP } from "@/constants/university"; import type { HomeUniversityName, HomeUniversitySlug } from "@/types/university"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import SearchResultsContent from "./SearchResultsContent"; @@ -29,12 +30,14 @@ export async function generateMetadata({ params }: PageProps): Promise if (!universityName) { return { title: "파견 학교 목록", + robots: NO_INDEX_ROBOTS, }; } return { title: `${universityName} 파견 학교 목록 | 솔리드커넥션`, description: `${universityName}에서 파견 가능한 교환학생 대학교 목록입니다. 지역별, 어학 요건별로 검색하고 관심있는 대학을 찾아보세요.`, + robots: NO_INDEX_ROBOTS, }; } diff --git a/apps/web/src/app/university/score/example/layout.tsx b/apps/web/src/app/university/score/example/layout.tsx new file mode 100644 index 00000000..1faf841a --- /dev/null +++ b/apps/web/src/app/university/score/example/layout.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +import { NO_INDEX_ROBOTS } from "@/utils/seo"; + +export const metadata: Metadata = { + title: "증명서 예시", + robots: NO_INDEX_ROBOTS, +}; + +const ScoreExampleLayout = ({ children }: { children: ReactNode }) => children; + +export default ScoreExampleLayout; diff --git a/apps/web/src/app/university/score/page.tsx b/apps/web/src/app/university/score/page.tsx index 013fd7be..39aaac63 100644 --- a/apps/web/src/app/university/score/page.tsx +++ b/apps/web/src/app/university/score/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import ScoreScreen from "./ScoreScreen"; export const metadata: Metadata = { title: "성적 확인하기", + robots: NO_INDEX_ROBOTS, }; const ScorePage = () => { diff --git a/apps/web/src/app/university/score/submit/gpa/page.tsx b/apps/web/src/app/university/score/submit/gpa/page.tsx index 0290eb48..63b2f17b 100644 --- a/apps/web/src/app/university/score/submit/gpa/page.tsx +++ b/apps/web/src/app/university/score/submit/gpa/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import GpaSubmitForm from "./GpaSubmitForm"; export const metadata: Metadata = { title: "성적 입력하기", + robots: NO_INDEX_ROBOTS, }; const SubmitGpaPage = () => { diff --git a/apps/web/src/app/university/score/submit/language-test/page.tsx b/apps/web/src/app/university/score/submit/language-test/page.tsx index 9325c986..ffdd4fe7 100644 --- a/apps/web/src/app/university/score/submit/language-test/page.tsx +++ b/apps/web/src/app/university/score/submit/language-test/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import LanguageTestSubmitForm from "./LanguageTestSubmitForm"; export const metadata: Metadata = { title: "성적 입력하기", + robots: NO_INDEX_ROBOTS, }; const SubmitLanguageTestPage = () => { diff --git a/apps/web/src/app/university/search/page.tsx b/apps/web/src/app/university/search/page.tsx index ad28e4eb..a097fb88 100644 --- a/apps/web/src/app/university/search/page.tsx +++ b/apps/web/src/app/university/search/page.tsx @@ -1,10 +1,12 @@ import type { Metadata } from "next"; import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; +import { NO_INDEX_ROBOTS } from "@/utils/seo"; import SearchClientContent from "./SearchClientContent"; export const metadata: Metadata = { title: "파견 학교 목록", + robots: NO_INDEX_ROBOTS, }; const Page = async () => { diff --git a/apps/web/src/utils/seo.ts b/apps/web/src/utils/seo.ts new file mode 100644 index 00000000..cddfaa22 --- /dev/null +++ b/apps/web/src/utils/seo.ts @@ -0,0 +1,66 @@ +import type { Metadata } from "next"; + +export const DEFAULT_SITE_URL = "https://www.solid-connection.com"; +export const DEFAULT_OG_IMAGE_PATH = "/opengraph-image.png"; + +const TRAILING_SLASHES = /\/+$/; +const HTML_TAGS = /<[^>]*>/g; +const WHITESPACE = /\s+/g; + +export const NO_INDEX_ROBOTS = { + index: false, + follow: false, + googleBot: { + index: false, + follow: false, + }, +} satisfies Metadata["robots"]; + +export const getSiteUrl = () => { + const siteUrl = process.env.NEXT_PUBLIC_WEB_URL || DEFAULT_SITE_URL; + return siteUrl.replace(TRAILING_SLASHES, ""); +}; + +export const isNonIndexSiteUrl = (siteUrl: string = getSiteUrl()) => { + const vercelEnv = process.env.VERCEL_ENV; + + return ( + vercelEnv === "preview" || + vercelEnv === "development" || + siteUrl.includes("stage") || + siteUrl.includes("localhost") || + siteUrl.includes("127.0.0.1") || + siteUrl.includes("[::1]") + ); +}; + +export const createUrl = (path: string = "/") => { + const normalizedPath = path.startsWith("/") ? path : `/${path}`; + return new URL(normalizedPath, `${getSiteUrl()}/`).toString(); +}; + +export const createAbsoluteUrl = (url: string | null | undefined, fallbackPath: string = DEFAULT_OG_IMAGE_PATH) => { + const trimmedUrl = url?.trim(); + + if (!trimmedUrl) { + return createUrl(fallbackPath); + } + + if (trimmedUrl.startsWith("http://") || trimmedUrl.startsWith("https://")) { + return trimmedUrl; + } + + return createUrl(trimmedUrl.startsWith("/") ? trimmedUrl : `/${trimmedUrl}`); +}; + +export const stripHtml = (value: string) => value.replace(HTML_TAGS, " ").replace(WHITESPACE, " ").trim(); + +export const truncateDescription = (value: string, maxLength: number = 150) => { + const text = stripHtml(value); + + if (text.length <= maxLength) { + return text; + } + + return `${text.slice(0, maxLength - 3).trim()}...`; +}; From 0b0741714d2289afedfd586c16a19f832f0f8bb2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Mon, 1 Jun 2026 16:19:25 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EC=9E=AC=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=ED=83=80=EC=9E=85=20=ED=98=B8=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/api/revalidate/route.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/api/revalidate/route.ts b/apps/web/src/app/api/revalidate/route.ts index 11a1568d..68a0d48b 100644 --- a/apps/web/src/app/api/revalidate/route.ts +++ b/apps/web/src/app/api/revalidate/route.ts @@ -10,6 +10,12 @@ interface RevalidateRequestBody { boardCode?: string; } +type RevalidateTagWithExpire = (tag: string, profile: { expire: number }) => void; + +const revalidateTagNow = (tag: string) => { + (revalidateTag as RevalidateTagWithExpire)(tag, { expire: 0 }); +}; + /** * @description ISR 페이지를 수동으로 revalidate하는 API * POST /api/revalidate @@ -57,7 +63,7 @@ async function POST(request: NextRequest) { // boardCode가 있으면 해당 커뮤니티 페이지 revalidate if (boardCode) { revalidatePath(`/community/${boardCode}`); - revalidateTag(`posts-${boardCode}`); + revalidateTagNow(`posts-${boardCode}`); return NextResponse.json({ revalidated: true, @@ -78,7 +84,7 @@ async function POST(request: NextRequest) { // 특정 태그 revalidate if (tag) { - revalidateTag(tag); + revalidateTagNow(tag); return NextResponse.json({ revalidated: true, message: `Tag ${tag} revalidated`, From 797be7404bc90666508fcd8d3bcbb486ad4ef2a4 Mon Sep 17 00:00:00 2001 From: manNomi Date: Tue, 2 Jun 2026 14:19:44 +0900 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20=EB=A6=AC=EB=B7=B0=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/my/match/page.tsx | 2 +- apps/web/src/app/sitemap.ts | 9 ++++++++- .../app/university/[homeUniversity]/[id]/page.tsx | 12 ++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/my/match/page.tsx b/apps/web/src/app/my/match/page.tsx index 25b7956d..c178ad69 100644 --- a/apps/web/src/app/my/match/page.tsx +++ b/apps/web/src/app/my/match/page.tsx @@ -6,7 +6,7 @@ import { NO_INDEX_ROBOTS } from "@/utils/seo"; import MatchContent from "./_ui/MatchContent"; export const metadata: Metadata = { - title: "프로필 수정", + title: "매칭 멘토", robots: NO_INDEX_ROBOTS, }; diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts index 067d6e39..c62f23ea 100644 --- a/apps/web/src/app/sitemap.ts +++ b/apps/web/src/app/sitemap.ts @@ -27,7 +27,14 @@ const getStaticRoutes = (): MetadataRoute.Sitemap => [ ]; const getUniversityDetailRoutes = async (): Promise => { - const universities = await getAllUniversities(); + let universities: Awaited>; + + try { + universities = await getAllUniversities(); + } catch { + return []; + } + const seenUrls = new Set(); return universities.flatMap((university) => { diff --git a/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx b/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx index 51924382..9734afd1 100644 --- a/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx +++ b/apps/web/src/app/university/[homeUniversity]/[id]/page.tsx @@ -123,22 +123,14 @@ const CollegeDetailPage = async ({ params }: PageProps) => { const universityDetailResult = await getUniversityDetailWithStatus(collegeId); if (!universityDetailResult.ok) { - const isNotFoundError = universityDetailResult.status === 404; - - if (isNotFoundError) { + if (universityDetailResult.status === 404) { notFound(); } return ( <> - + ); } From a746008a05ff5005783cf110c48ba1392f7ce755 Mon Sep 17 00:00:00 2001 From: manNomi Date: Tue, 2 Jun 2026 14:59:13 +0900 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20=EB=A9=98=ED=86=A0=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=95=98=EB=8B=A8=20=EC=97=AC=EB=B0=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx b/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx index 6c088805..ec91d6e0 100644 --- a/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx +++ b/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx @@ -43,7 +43,7 @@ const MentorFindSection = () => { {/* 멘토 리스트 */} -
+
{mentorList.length === 0 ? ( ) : ( From 8b4b12cab5e01f1042936504aa51a723357591c2 Mon Sep 17 00:00:00 2001 From: manNomi Date: Tue, 2 Jun 2026 15:13:16 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EC=B1=84=ED=8C=85=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=20=EA=B5=AC=EB=B6=84=EC=84=A0=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EB=B0=98=EC=A0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx index 83475a2b..50353ca0 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx @@ -155,7 +155,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => { {/* 날짜 구분선 */} {showDateSeparator && (
- + {formatDateSeparator(message.createdAt)}
From d168892da561e5a13bc2e45a7ae3534e98b29f8d Mon Sep 17 00:00:00 2001 From: manNomi Date: Tue, 2 Jun 2026 15:36:16 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20=EB=A9=98=ED=86=A0=20UI=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=B3=80=EA=B2=BD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx | 2 +- apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx b/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx index ec91d6e0..6c088805 100644 --- a/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx +++ b/apps/web/src/app/mentor/_ui/MentorClient/_ui/MentorFindSection/index.tsx @@ -43,7 +43,7 @@ const MentorFindSection = () => {
{/* 멘토 리스트 */} -
+
{mentorList.length === 0 ? ( ) : ( diff --git a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx index 50353ca0..83475a2b 100644 --- a/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx +++ b/apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx @@ -155,7 +155,7 @@ const ChatContent = ({ chatId }: ChatContentProps) => { {/* 날짜 구분선 */} {showDateSeparator && (
- + {formatDateSeparator(message.createdAt)}