-
Notifications
You must be signed in to change notification settings - Fork 3
✨ 대학 카탈로그 SSG 실패 시 CSR 폴백 추가 #623
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
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 |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| "use client"; | ||
|
|
||
| import { useGetUniversityDetail } from "@/apis/universities"; | ||
|
|
||
| import UniversityDetail from "./UniversityDetail"; | ||
| import UniversityDetailPreparingFallback from "./UniversityDetailPreparingFallback"; | ||
|
|
||
| interface UniversityDetailCsrFallbackProps { | ||
| universityId: number; | ||
| backHref: string; | ||
| } | ||
|
|
||
| /** | ||
| * SSG(빌드 시점) 또는 서버 렌더 단계에서 대학 상세 데이터를 가져오지 못했을 때 사용하는 클라이언트 폴백. | ||
| * 정적 생성 실패를 빈 화면으로 넘기지 않고, 브라우저에서 같은 API를 다시 조회해 내용을 채운다. | ||
| */ | ||
| const UniversityDetailCsrFallback = ({ universityId, backHref }: UniversityDetailCsrFallbackProps) => { | ||
| const { data: university, isPending, isError } = useGetUniversityDetail(universityId); | ||
|
|
||
| if (isPending) { | ||
| return ( | ||
| <UniversityDetailPreparingFallback | ||
| backHref={backHref} | ||
| title="대학 정보를 불러오는 중입니다." | ||
| description="잠시만 기다려주세요." | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| if (isError || !university) { | ||
| return <UniversityDetailPreparingFallback backHref={backHref} />; | ||
| } | ||
|
|
||
| return ( | ||
| <div className="w-full"> | ||
| <UniversityDetail koreanName={university.koreanName} university={university} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default UniversityDetailCsrFallback; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import type { Metadata } from "next"; | ||
| import { notFound } from "next/navigation"; | ||
|
|
||
| import { getAllUniversities, getUniversityDetailWithStatus } from "@/apis/universities/server"; | ||
| import { getAllUniversitiesSafe, getUniversityDetailWithStatus } from "@/apis/universities/server"; | ||
| import TopDetailNavigation from "@/components/layout/TopDetailNavigation"; | ||
| import { getHomeUniversityBySlug, HOME_UNIVERSITY_SLUGS } from "@/constants/university"; | ||
| import type { HomeUniversitySlug } from "@/types/university"; | ||
|
|
@@ -10,10 +10,11 @@ import { createUrl, NO_INDEX_ROBOTS } from "@/utils/seo"; | |
|
|
||
| // UniversityDetail 컴포넌트 | ||
| import UniversityDetail from "./_ui/UniversityDetail"; | ||
| import UniversityDetailPreparingFallback from "./_ui/UniversityDetailPreparingFallback"; | ||
| import UniversityDetailCsrFallback from "./_ui/UniversityDetailCsrFallback"; | ||
|
|
||
| export const revalidate = false; | ||
| export const dynamicParams = false; | ||
| // 정적 생성에 실패해 목록에 빠진 경로도 요청 시점에 렌더링한다(404 대신 CSR 폴백으로 이어짐). | ||
| export const dynamicParams = true; | ||
|
Comment on lines
+16
to
+17
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.
With Useful? React with 👍 / 👎. |
||
|
|
||
| // 모든 homeUniversity + id 조합에 대해 정적 경로 생성 | ||
| export async function generateStaticParams() { | ||
|
|
@@ -24,11 +25,13 @@ export async function generateStaticParams() { | |
| return { slug, universities: [] }; | ||
| } | ||
|
|
||
| const universities = await getAllUniversities({ | ||
| // 조회 실패 시 빌드를 중단하지 않고 해당 홈 대학 경로만 건너뛴다. | ||
| // 빠진 경로는 dynamicParams=true 덕분에 요청 시점에 렌더링되고, 그때도 실패하면 CSR 폴백이 받는다. | ||
| const universities = await getAllUniversitiesSafe({ | ||
| homeUniversityId: homeUniversityInfo.homeUniversityId, | ||
| }); | ||
|
|
||
| return { slug, universities }; | ||
| return { slug, universities: universities ?? [] }; | ||
| }), | ||
| ); | ||
|
|
||
|
|
@@ -159,10 +162,11 @@ const CollegeDetailPage = async ({ params }: PageProps) => { | |
| notFound(); | ||
| } | ||
|
|
||
| // 서버에서 데이터를 못 가져온 경우, 준비중 화면으로 굳히지 않고 브라우저에서 다시 조회한다. | ||
| return ( | ||
| <> | ||
| <TopDetailNavigation title="파견 학교 상세" backHref={`/university/${homeUniversity}`} /> | ||
| <UniversityDetailPreparingFallback backHref={`/university/${homeUniversity}`} /> | ||
| <UniversityDetailCsrFallback universityId={collegeId} backHref={`/university/${homeUniversity}`} /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| "use client"; | ||
|
|
||
| import { useQuery } from "@tanstack/react-query"; | ||
|
|
||
| import { QueryKeys } from "@/apis/queryKeys"; | ||
| import { type SearchTextResponse, universitiesApi } from "@/apis/universities/api"; | ||
| import type { HomeUniversitySlug, ListUniversity } from "@/types/university"; | ||
|
|
||
| import UniversityListContent from "./UniversityListContent"; | ||
|
|
||
| interface UniversityListCsrFallbackProps { | ||
| homeUniversityId: number; | ||
| homeUniversitySlug: HomeUniversitySlug; | ||
| } | ||
|
|
||
| /** | ||
| * SSG(빌드 시점) 또는 서버 렌더 단계에서 파견학교 목록을 가져오지 못했을 때 사용하는 클라이언트 폴백. | ||
| * 빈 목록을 정적으로 굳혀버리지 않고, 브라우저에서 같은 API를 다시 조회한다. | ||
| */ | ||
| const UniversityListCsrFallback = ({ homeUniversityId, homeUniversitySlug }: UniversityListCsrFallbackProps) => { | ||
| const { | ||
| data: universities, | ||
| isPending, | ||
| isError, | ||
| } = useQuery<SearchTextResponse, Error, ListUniversity[]>({ | ||
| queryKey: [QueryKeys.universities.searchText, { homeUniversityId }], | ||
| queryFn: () => universitiesApi.getSearchText({ value: "", homeUniversityId }), | ||
|
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.
When Useful? React with 👍 / 👎. |
||
| select: (data) => data.univApplyInfoPreviews, | ||
| }); | ||
|
|
||
| if (isPending) { | ||
| return ( | ||
| <div | ||
| className="flex min-h-[50vh] items-center justify-center px-5 text-center text-k-400 typo-regular-3" | ||
| role="status" | ||
| aria-live="polite" | ||
| > | ||
| 파견학교 목록을 불러오는 중입니다. | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| if (isError || !universities) { | ||
| return ( | ||
| <div | ||
| className="flex min-h-[50vh] flex-col items-center justify-center px-5 text-center" | ||
| role="status" | ||
| aria-live="polite" | ||
| > | ||
| <p className="text-k-700 typo-sb-9">목록을 불러오지 못했습니다.</p> | ||
| <p className="mt-1 text-k-400 typo-regular-3">잠시 후 다시 확인해주세요.</p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return <UniversityListContent universities={universities} homeUniversitySlug={homeUniversitySlug} />; | ||
| }; | ||
|
|
||
| export default UniversityListCsrFallback; | ||
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.
This runbook now requires SSG fetch failures to complete the build with a CSR fallback, but the referenced
docs/university-multizone-deployment.mdverification section still says such a fetch failure must fail the build. Anyone following the documented cold-build check will treat the newly intended result as a regression, so the deployment reference needs to be updated with this behavior change.AGENTS.md reference: AGENTS.md:L94-L100
Useful? React with 👍 / 👎.