feat: 대학 정보 멀티존 분리#543
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (4)
Walkthrough
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b3f301ab1b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| const universityData = universityDetailResult.data; | ||
| const universityData = await getUniversityDetailForSsg(collegeId); |
There was a problem hiding this comment.
Preserve the detail fallback on non-OK API responses
When a preview ID from generateStaticParams() has a detail endpoint that returns any non-OK response, getUniversityDetailForSsg() throws via assertUniversitySsgResponse; the previous page used getUniversityDetailWithStatus() and rendered UniversityDetailPreparingFallback for non-404 failures. In that scenario, one preparing or temporarily failing university detail now breaks the university-web static build or produces an error page instead of the intended fallback, so this should keep the status-aware fetch path before rendering detail content.
Useful? React with 👍 / 👎.
| }; | ||
|
|
||
| const UniversityListContent = (props: UniversityListContentProps) => ( | ||
| <Suspense fallback={null}> |
There was a problem hiding this comment.
Avoid null prerender for the catalog list
For these statically generated /university/:homeUniversity pages, useSearchParams() inside the child causes the subtree up to this Suspense boundary to be client-rendered; because the fallback is null, the prerendered HTML contains no search bar, count, or university cards until hydration. The old implementation parsed searchParams in the server page and still emitted the list in the initial HTML, so crawlers/no-JS users and slow clients now see a blank catalog body; pass the initial query values from the page or use a meaningful server-rendered fallback instead of suspending the whole list to null.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 20
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (16)
apps/university-web/src/apis/chat/normalize.ts-62-92 (1)
62-92:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win서버 id가 빠진 동일 메시지끼리 폴백 id가 겹칠 수 있어요.
getFallbackMessageId는senderId|createdAt|content|attachmentSignature만으로 해시를 만들기 때문에, 같은 사용자가 같은 초에 동일한 내용을 두 번 보내면 완전히 다른 메시지인데도 같은 음수 id가 생성됩니다. 이렇게 되면 리스트 렌더링 시 React key가 충돌해 메시지가 섞이거나 누락되어 보일 수 있습니다.
normalizeChatMessage를 호출하는 쪽(.map(normalizeChatMessage))에서 이미 배열 인덱스를 받을 수 있으니, 인덱스를 시드에 함께 섞어주면 적은 비용으로 충돌을 피할 수 있습니다.🧩 인덱스를 시드에 포함하는 제안
-const getFallbackMessageId = (message: RawChatMessage): number => { +const getFallbackMessageId = (message: RawChatMessage, index = 0): number => { const senderId = toNumber(message.senderId ?? message.siteUserId); const attachmentSignature = (message.attachments ?? []) .map((attachment) => `${attachment.isImage ? "image" : "file"}:${attachment.url}:${attachment.createdAt}`) .join(","); - const seed = `${senderId}|${message.createdAt}|${message.content}|${attachmentSignature}`; + const seed = `${index}|${senderId}|${message.createdAt}|${message.content}|${attachmentSignature}`; // 서버에서 id가 누락되는 경우를 대비해 항상 동일한 임시 음수 id를 생성합니다. return -createStableHash(seed); }; @@ -export const normalizeChatMessage = (message: RawChatMessage): ChatMessage => { +export const normalizeChatMessage = (message: RawChatMessage, index = 0): ChatMessage => { const parsedId = toNumber(message.id); - const normalizedId = parsedId > 0 ? parsedId : getFallbackMessageId(message); + const normalizedId = parsedId > 0 ? parsedId : getFallbackMessageId(message, index);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/chat/normalize.ts` around lines 62 - 92, getFallbackMessageId can produce identical negative ids for distinct messages sent by the same user at the same timestamp; fix by including the message array index in the seed. Change getFallbackMessageId to accept an additional index parameter (e.g. getFallbackMessageId(message, index)) and include that index in the seed string, update normalizeChatMessage signature to accept an optional index (normalizeChatMessage(message, index)) and when mapping messages call it with the map index (e.g. messages.map((m, i) => normalizeChatMessage(m, i))). This keeps fallback ids stable but unique per position and only affects cases where parsed id <= 0.apps/university-web/src/apis/Auth/postEmailVerification.ts-9-13 (1)
9-13:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win파일명과 실제 동작이 어긋나 혼동을 줍니다.
파일명은
postEmailVerification.ts인데, 내용은usePostEmailSignUp이authApi.postEmailSignUp(이메일 회원가입)을 호출합니다. 검증(verification)과 회원가입(sign-up)은 별개 흐름이므로, 파일/훅 이름을 동작에 맞춰 정리하면 유지보수 시 오해를 막을 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/postEmailVerification.ts` around lines 9 - 13, The file name and implementation disagree: postEmailVerification.ts contains a hook named usePostEmailSignUp that calls authApi.postEmailSignUp; choose one consistent behavior and update names/calls accordingly — either rename the file to postEmailSignUp.ts (or similar) to match usePostEmailSignUp and keep calling authApi.postEmailSignUp, or change the hook to usePostEmailVerification and its mutationFn to call authApi.postEmailVerification so the file, hook name, and API call all reflect the verification flow; update any imports/usages to the new name to avoid breaking references.apps/university-web/src/apis/Auth/deleteAccount.ts-17-28 (1)
17-28:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win탈퇴 요청 실패 시 사용자가 로그인 상태로 홈에 남는 어긋남이 있습니다.
onMutate에서 먼저router.replace("/")로 이동하지만, 토큰 정리(clearAccessToken)와 캐시 비우기는onSuccess에서만 수행됩니다. 그래서deleteAccount가 실패하면 사용자는 이미 홈으로 이동했음에도 토큰이 남아 여전히 로그인 상태가 되어, "탈퇴된 줄 알았는데 아니다"라는 혼란을 줄 수 있습니다.onError로 실패를 안내하거나, 낙관적 이동 대신onSuccess에서 이동하도록 정리하는 편이 안전합니다.🛟 제안 변경
return useMutation<AccountResponse, AxiosError, void>({ mutationFn: () => authApi.deleteAccount(), - onMutate: () => { - // 낙관적 업데이트: 요청이 시작되면 바로 홈으로 이동 - router.replace("/"); - }, onSuccess: () => { // Zustand persist가 자동으로 localStorage에서 제거 clearAccessToken(); queryClient.clear(); + router.replace("/"); + }, + onError: () => { + // 실패 시 사용자에게 안내 (로그인 상태 유지) + showIconToast("error", "회원탈퇴에 실패했습니다. 다시 시도해 주세요."); }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/deleteAccount.ts` around lines 17 - 28, The current optimistic navigation in useMutation (onMutate -> router.replace("/")) moves the user before authApi.deleteAccount completes, while clearAccessToken() and queryClient.clear() run only in onSuccess, causing the user to remain logged if the request fails; update the mutation to perform router.replace("/"), clearAccessToken(), and queryClient.clear() inside onSuccess instead of onMutate, and add an onError handler that shows an error (or reverts navigation) so failures keep the user logged in and informed (refer to useMutation's onMutate, onSuccess, onError, authApi.deleteAccount, clearAccessToken, router.replace, and queryClient.clear).apps/university-web/src/apis/Auth/api.ts-44-51 (1)
44-51:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
EmailVerification타입이 이메일 “검증” 흐름에 연결되지 않는 상태입니다.
apps/university-web/src/apis/Auth/api.ts에EmailVerificationRequest/Response(verificationCode포함 )만 있고,authApi안에 이를 쓰는 인증코드 검증 메서드(postEmailVerification등)는 보이지 않습니다.apps/university-web/src/apis/Auth/postEmailVerification.ts는usePostEmailSignUp훅으로 동작하며authApi.postEmailSignUp만 호출해EmailVerification*타입을 전혀 사용하지 않습니다. (파일명/훅명도postEmailVerificationvsusePostEmailSignUp로 어긋나 있어요.)apps/university-web/src/apis/queryKeys.ts의emailVerificationqueryKey도 실제 호출부가 확인되지 않습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/api.ts` around lines 44 - 51, The EmailVerification types are declared but never wired into the API surface or hooks; add a dedicated method authApi.postEmailVerification that accepts EmailVerificationRequest and returns EmailVerificationResponse, update apps/university-web/src/apis/Auth/postEmailVerification.ts to call authApi.postEmailVerification (not authApi.postEmailSignUp), rename the hook to usePostEmailVerification (or align the file/hook name) so it uses the verificationCode, and ensure queryKeys.emailVerification is referenced where the verification call is made; adjust imports and tests accordingly to use EmailVerificationRequest/Response and the new authApi method.apps/university-web/src/apis/Auth/postSignOut.ts-14-23 (1)
14-23:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win서버 로그아웃이 실패하면 로컬 세션이 그대로 남습니다.
정리 로직이
onSuccess에만 있어서, 네트워크 오류나 서버 5xx로postSignOut()이 실패하면clearAccessToken()과queryClient.clear()가 호출되지 않아 사용자가 로그아웃하지 못한 채 갇히게 됩니다. 로그아웃은 로컬 정리가 우선되어야 하므로, 성공/실패와 무관하게 정리되도록onSettled로 옮기는 것을 권합니다.🛡️ 제안
return useMutation<SignOutResponse, AxiosError, void>({ mutationFn: () => authApi.postSignOut(), - onSuccess: () => { - // Zustand persist가 자동으로 localStorage에서 제거 - clearAccessToken(); - queryClient.clear(); - // 로그아웃 후 홈으로 리다이렉트 - window.location.href = "/"; - }, + onSettled: () => { + // 서버 응답과 무관하게 로컬 인증 상태를 정리 + clearAccessToken(); + queryClient.clear(); + window.location.href = "/"; + }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/postSignOut.ts` around lines 14 - 23, Move the local cleanup from the mutation's onSuccess to onSettled so cleanup runs regardless of server response: in the useMutation call that invokes authApi.postSignOut() (see mutationFn/postSignOut), remove clearAccessToken() and queryClient.clear() from onSuccess and place them into an onSettled handler that always calls clearAccessToken() and queryClient.clear(), while keeping the redirect to "/" in onSuccess if you still want to navigate only on successful server sign-out.apps/university-web/src/apis/Auth/index.ts-16-25 (1)
16-25:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
usePostRefreshToken배럴 누락 + 앱 내 사용 흔적 부재 확인 필요
- 배럴 재-export 누락
apps/university-web/src/apis/Auth/index.ts에usePostRefreshToken이 재-export되어 있지 않습니다.- 실사용 흔적 없음(죽은 코드 가능성 큼)
apps/university-web/src에서usePostRefreshToken은apps/university-web/src/apis/Auth/postRefreshToken.ts내부 선언/내보내기 라인에서만 확인되고, 해당 훅을 가져다 쓰는import/사용처가 보이지 않습니다.- 결정/정리 제안
- 실제로 쓰려던 훅이면
index.ts에 재-export 추가(또는 실제 호출자가 있다면 import 경로 확인)로 접근성을 맞추고,- 안 쓰는 훅이면
postRefreshToken.ts정리(삭제/제거)로 죽은 코드 리스크를 줄이세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/index.ts` around lines 16 - 25, The barrel file is missing the re-export for the hook usePostRefreshToken declared in postRefreshToken.ts and there are no usages in the codebase (possible dead code); either add a re-export for usePostRefreshToken in index.ts alongside the other exports so callers can import it, or if the hook is unused, remove/clean up postRefreshToken.ts to eliminate dead code—search for usePostRefreshToken and postRefreshToken.ts and then either add the export to index.ts or delete the unused file accordingly.apps/university-web/src/apis/kakao-api/api.ts-3-9 (1)
3-9:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winKakao 응답 타입을
undefined로 두면data필드 접근이 타입에서 막힙니다 🧩
apps/university-web/src/apis/kakao-api/api.ts에서KakaoUserIdsResponse/KakaoUnlinkResponse/KakaoInfoResponse가 전부undefined로 선언돼 있어res.data와 React Query 훅의data타입이 전부undefined로 추론됩니다.
그 결과getKakaoInfo에서kakao_account.email같은 값을 캐스팅/타입 무시 없이 읽기 어렵습니다.export type KakaoUserIdsResponse = undefined; export type KakaoUnlinkResponse = undefined; export type KakaoUnlinkRequest = Record<string, never>; export type KakaoInfoResponse = undefined;
v2/user/me는kakao_account.email을 포함하는 형태이므로 문서 JSON 스키마대로KakaoInfoResponse타입을 구체화해 주세요.v1/user/ids는elements(ID 배열) 중심으로 타입을 잡아 주세요.v1/user/unlink는 성공/실패 바디 포맷이 문서 예시 기준으로 달라질 수 있으니(특히 성공 응답 바디) 카카오 문서의 응답 예시를 확인한 뒤KakaoUnlinkResponse를 확정해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/kakao-api/api.ts` around lines 3 - 9, The current response types (KakaoUserIdsResponse, KakaoUnlinkResponse, KakaoInfoResponse) are all declared as undefined which prevents accessing res.data and breaks getKakaoInfo; update these to concrete interfaces that match Kakao docs: define KakaoInfoResponse to include the top-level id and kakao_account with email (and other optional fields) to allow reading kakao_account.email in getKakaoInfo, define KakaoUserIdsResponse to include elements: string[] (and any pagination/meta fields per v1/user/ids), and set KakaoUnlinkResponse to the documented v1/user/unlink success/error shape (or a union if it varies); keep KakaoUnlinkRequest as Record<string, never> if correct, and then update any usages (e.g., getKakaoInfo and React Query hooks) to use these new types so res.data is properly typed.apps/university-web/src/apis/MyPage/getProfile.ts-24-34 (1)
24-34:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win낙관적 병합이 요청 객체를 통째로 펼치고 있어요.
select가 변이 변수를Partial<MyInfoResponse>로 캐스팅하지만, 실제 변수 타입은ProfilePatchRequest(nickname?,file?)예요. 그래서{ ...queryResult.data, ...pendingData }로 병합하면 사용자 정보에File객체인file속성이 그대로 끼어들고, 정작 표시용 프로필 이미지(URL)는 낙관적으로 갱신되지 않습니다.또한 호출부가
nickname: undefined를 포함해 넘기면 기존 닉네임이undefined로 덮여 화면에서 잠깐 비어 보일 수 있어요. 병합 대상을 닉네임처럼 실제로 표시 가능한 필드로 좁혀 두는 편이 안전합니다.🛠️ 병합 필드를 좁히는 제안
- const pendingMutations = useMutationState({ - filters: { - mutationKey: [QueryKeys.MyPage.profile, "patch"], - status: "pending", - }, - select: (mutation) => { - return mutation.state.variables as Partial<MyInfoResponse>; - }, - }); - - const isOptimistic = pendingMutations.length > 0; - const pendingData = isOptimistic ? pendingMutations[0] : null; - - const displayData = isOptimistic && queryResult.data ? { ...queryResult.data, ...pendingData } : queryResult.data; + const pendingMutations = useMutationState({ + filters: { + mutationKey: [QueryKeys.MyPage.profile, "patch"], + status: "pending", + }, + select: (mutation) => mutation.state.variables as ProfilePatchRequest, + }); + + const pendingData = pendingMutations.at(0); + const displayData = + pendingData && queryResult.data + ? { ...queryResult.data, ...(pendingData.nickname ? { nickname: pendingData.nickname } : {}) } + : queryResult.data;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/MyPage/getProfile.ts` around lines 24 - 34, The optimistic merge is spreading the mutation variables (ProfilePatchRequest) into MyInfoResponse, which can inject a File under `file` and overwrite displayable fields with undefined; modify the merge so it only applies explicit displayable fields (e.g., `nickname`, and `profileImageUrl` derived after file upload) from the pending mutation: in the `select`/merge logic (look at `select`, `pendingMutations`, `displayData`, and `queryResult.data`) pick or map only allowed keys, ignore `file` directly, and filter out undefined values so existing profile fields are not overwritten by undefined.apps/university-web/src/apis/news/deleteNews.ts-6-39 (1)
6-39:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1. 캐시 타입과 컨텍스트 타입이 실제 저장 형태와 어긋나 있어요.
이 쿼리 키에는
setQueryData<ArticleListResponse>(...)로ArticleListResponse객체가 저장되는데, 스냅샷은getQueryData<Article[]>로 읽고 컨텍스트도Article[]로 선언되어 있습니다. 런타임에서는 저장된 객체가 그대로 복원되어 롤백 자체는 동작하지만, 타입 정보가 실제와 달라서 이후previousArticleList를 다룰 때 잘못된 형태를 가정하기 쉬워집니다.ArticleListResponse로 통일해 두면 타입과 실제 데이터가 일치해 안전해집니다.🔧 제안: 타입 일치 및 미사용 import 정리
-import type { Article } from "`@/types/news`"; import { type ArticleListResponse, NewsQueryKeys, newsApi } from "./api"; type ArticleDeleteMutationContext = { - previousArticleList?: Article[]; + previousArticleList?: ArticleListResponse; };- const previousArticleList = queryClient.getQueryData<Article[]>(queryKey); + const previousArticleList = queryClient.getQueryData<ArticleListResponse>(queryKey);onError: (_error, _variables, context) => { if (context?.previousArticleList) { - queryClient.setQueryData<Article[]>(queryKey, context.previousArticleList); + queryClient.setQueryData<ArticleListResponse>(queryKey, context.previousArticleList); } },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/news/deleteNews.ts` around lines 6 - 39, The context and cache types are inconsistent: you store an ArticleListResponse via setQueryData but read and snapshot as Article[]; update the context type ArticleDeleteMutationContext to hold previousArticleList?: ArticleListResponse, change the getQueryData call to getQueryData<ArticleListResponse>(queryKey) and ensure the returned snapshot and onError restore use ArticleListResponse (not Article[]), and adjust any setQueryData/getQueryData generic parameters in useDeleteArticle (mutationFn/newsApi.deleteArticle, queryKey, onMutate, onError) so types match the actual stored shape; also remove any now-unused imports related to Article[] if present.apps/university-web/src/apis/news/putUpdateNews.ts-6-48 (1)
6-48:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1. 스냅샷/컨텍스트 타입을
ArticleListResponse로 맞춰 주세요.
deleteNews.ts,postLikeNews.ts와 동일한 패턴으로, 캐시에는ArticleListResponse가 저장되지만 여기서는Article[]로 읽고 선언하고 있어요. 롤백은 같은 객체를 되돌려서 동작하지만, 타입과 실제 데이터가 일치하도록 정리하면 이후 유지보수가 더 안전합니다.🔧 제안: 타입 일치 및 미사용 import 정리
-import type { Article } from "`@/types/news`"; import { type ArticleListResponse, NewsQueryKeys, newsApi, type UsePutModifyArticleRequest } from "./api"; type ArticleMutationContext = { - previousArticleList?: Article[]; + previousArticleList?: ArticleListResponse; };- const previousArticleList = queryClient.getQueryData<Article[]>(queryKey); + const previousArticleList = queryClient.getQueryData<ArticleListResponse>(queryKey);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/news/putUpdateNews.ts` around lines 6 - 48, Change the snapshot/context type to ArticleListResponse throughout: update the ArticleMutationContext so previousArticleList is ArticleListResponse | undefined, adjust the usePutModifyArticle onMutate to call queryClient.getQueryData<ArticleListResponse>(queryKey) (not Article[]), return that previousArticleList, and ensure onError sets the query data with that ArticleListResponse value; also update any related generics/usages of setQueryData/getQueryData in usePutModifyArticle to ArticleListResponse and remove any now-unused imports.apps/university-web/src/apis/news/postLikeNews.ts-7-49 (1)
7-49:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1. 컨텍스트/스냅샷 타입이 실제 캐시 형태와 다릅니다.
deleteNews.ts와 동일하게, 이 쿼리 키에는ArticleListResponse가 저장되는데 스냅샷은Article[]로 읽고 컨텍스트도Article[]로 선언되어 있어요. 런타임 롤백은 동작하지만 타입이 실제와 어긋나니ArticleListResponse로 맞추는 편이 안전합니다.2. 좋아요 수치 정산 단계가 없습니다.
deleteLikeNews.ts와 마찬가지로onSettled재조회가 없어 서버likeCount와 표시값이 어긋날 수 있으니 함께 보강하면 좋습니다.🔧 제안: 타입 일치 + 정산 단계
-import type { Article } from "`@/types/news`"; import { type ArticleListResponse, NewsQueryKeys, newsApi, type PostArticleLikeResponse } from "./api"; type ArticleLikeMutationContext = { - previousArticleList?: Article[]; + previousArticleList?: ArticleListResponse; };- const previousArticleList = queryClient.getQueryData<Article[]>(queryKey); + const previousArticleList = queryClient.getQueryData<ArticleListResponse>(queryKey);onError: (_err, _variables, context) => { if (context?.previousArticleList) { - queryClient.setQueryData<Article[]>(queryKey, context.previousArticleList); + queryClient.setQueryData<ArticleListResponse>(queryKey, context.previousArticleList); } }, + + onSettled: () => { + queryClient.invalidateQueries({ queryKey }); + }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/news/postLikeNews.ts` around lines 7 - 49, The mutation context and snapshot should match the cached shape and you should add a post-settlement refresh: change ArticleLikeMutationContext (and any uses in onMutate/onError) so previousArticleList is an ArticleListResponse (not Article[]), update the get/setQueryData calls in usePostArticleLike (function name) to read/write ArticleListResponse consistently for queryKey, and add an onSettled handler that refetches/invalidate the queryKey (e.g., queryClient.invalidateQueries or refetchQueries) to reconcile server likeCount after the optimistic update.apps/university-web/COMPONENTS.md-399-399 (1)
399-399:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1) 리소스 링크가 플레이스홀더 상태입니다.
- Line 399의 `link-to-figma`는 실제 이동이 불가합니다. 온보딩 문서 신뢰도를 위해 실 URL로 교체하거나, 미정이면 TODO 표기로 의도를 명확히 해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/COMPONENTS.md` at line 399, Replace the placeholder markdown link "[Figma Design System](link-to-figma)" with the actual Figma URL for the project's design system or, if the URL is not yet available, change the link target to a clear TODO marker (e.g., "[Figma Design System](TODO: add figma URL)") and optionally add a short comment note beside "Figma Design System" indicating who should supply the link; update the entry so it is not a dead placeholder and is clearly actionable.apps/university-web/REACT_COMPILER.md-23-24 (1)
23-24:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1) 검증 명령의 패키지 타깃이 현재 앱과 다릅니다.
- Line 23~24는 `@solid-connect/web`를 검사하고 있어 `apps/university-web` 롤아웃 검증과 불일치합니다. 문서 목적에 맞게 필터 대상을 university-web로 맞춰 주세요.🔧 제안 수정안
-- Run `pnpm --filter `@solid-connect/web` build` and confirm the compiled server/client output references `react/compiler-runtime`. -- Run `pnpm --filter `@solid-connect/web` ci:check` before merging. +- Run `pnpm --filter `@solid-connect/university-web` build` and confirm the compiled server/client output references `react/compiler-runtime`. +- Run `pnpm --filter `@solid-connect/university-web` ci:check` before merging.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/REACT_COMPILER.md` around lines 23 - 24, The two verification commands in REACT_COMPILER.md target the wrong package (`@solid-connect/web`); update both commands so the --filter target matches the current app (use university-web or the package name used in your repo, e.g., --filter university-web) so that the build and ci:check steps run against the apps/university-web project (change the lines that run `pnpm --filter `@solid-connect/web` build` and `pnpm --filter `@solid-connect/web` ci:check` to use the correct filter).apps/university-web/AUTHENTICATION.md-34-60 (1)
34-60:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win1) 문서의 대상 앱 경로가 현재 코호트와 어긋나 있습니다.
- Line 34~60에서 `apps/web/...` 경로를 기준으로 설명하고 있어, `apps/university-web` 문서로 읽을 때 책임 경계와 추적 경로가 혼동됩니다. 같은 문서 내 경로 기준을 university-web로 맞춰 주세요.🔧 제안 수정안(예시)
-`apps/web/src/middleware.ts`는 현재 아래만 담당합니다. +`apps/university-web/src/middleware.ts`는 현재 아래만 담당합니다. -- `apps/web/src/lib/zustand/useAuthStore.ts` -- `apps/web/src/utils/axiosInstance.ts` -- `apps/web/src/components/layout/ReissueProvider/index.tsx` -- `apps/web/src/middleware.ts` +- `apps/university-web/src/lib/zustand/useAuthStore.ts` +- `apps/university-web/src/utils/axiosInstance.ts` +- `apps/university-web/src/components/layout/ReissueProvider/index.tsx` +- `apps/university-web/src/middleware.ts`🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/AUTHENTICATION.md` around lines 34 - 60, The doc uses paths like `apps/web/...` which don't match this repo's app name (`university-web`), causing confusion; update all path references in AUTHENTICATION.md (e.g., entries under "Related Files" and anywhere `apps/web/src/...` is mentioned, including `apps/web/src/middleware.ts`, `apps/web/src/lib/zustand/useAuthStore.ts`, `apps/web/src/utils/axiosInstance.ts`, `apps/web/src/components/layout/ReissueProvider/index.tsx`) to the correct `apps/university-web/...` equivalents (or make them relative like `src/...` if that better reflects the project layout) so responsibility and traceability align with the current codebase.apps/university-web/next.config.mjs-63-65 (1)
63-65:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
typescript.ignoreBuildErrors: true를 유지할 근거(또는 보강) 필요
- CI에서는
university-web-quality-check가pnpm --filter@solid-connect/university-webrun ci:check로typecheck:ci(tsc --noEmit -p tsconfig.ci.json)를 먼저 통과해야university-web-build가 실행되므로, 해당 워크플로우 기준으로 “타입 계약이 깨진 채 배포된다”는 우려는 과장입니다.
- 다만
ignoreBuildErrors: true는next build단계에서 타입 오류를 실패로 만들지 않을 수 있으니(로컬/수동/다른 파이프라인 포함),ignoreBuildErrors를false로 두거나 이 설정을 둔 이유를 명시하는 쪽이 더 안전합니다.수정 예시
typescript: { - ignoreBuildErrors: true, + ignoreBuildErrors: false, },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/next.config.mjs` around lines 63 - 65, The next.config.mjs currently sets typescript.ignoreBuildErrors: true which can mask type errors during next build; either set typescript.ignoreBuildErrors to false in next.config.mjs (update the typescript config entry) or add a clear code comment and CI-proof justification near the typescript.ignoreBuildErrors setting referencing the existing CI job (university-web-quality-check / typecheck:ci) and why this repo still needs true; update the next.config.mjs entry for the typescript key (typescript.ignoreBuildErrors) accordingly so intent is explicit.apps/university-web/src/apis/Admin/index.ts-2-5 (1)
2-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win훅 배럴 export 이름에
use접두사를 유지하세요
apps/university-web/src/apis/Admin/index.ts배럴은 실제 구현(useGetGpaList,usePutVerifyGpa,useGetLanguageTestList,usePutVerifyLanguageTest)이 훅인데도getGpaList,putVerifyGpa같은 이름으로 내보내고 있습니다.- 훅임이 이름에서 바로 드러나도록
use접두사를 포함한 이름으로 re-export하는 편이 안전합니다.제안 diff
export { adminApi } from "./api"; -export { default as getGpaList } from "./getGpaList"; -export { default as getLanguageTestList } from "./getLanguageTestList"; -export { default as putVerifyGpa } from "./putVerifyGpa"; -export { default as putVerifyLanguageTest } from "./putVerifyLanguageTest"; +export { default as useGetGpaList } from "./getGpaList"; +export { default as useGetLanguageTestList } from "./getLanguageTestList"; +export { default as usePutVerifyGpa } from "./putVerifyGpa"; +export { default as usePutVerifyLanguageTest } from "./putVerifyLanguageTest";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Admin/index.ts` around lines 2 - 5, The barrel in Admin/index.ts is re-exporting hooks without the "use" prefix (exporting getGpaList, putVerifyGpa, getLanguageTestList, putVerifyLanguageTest) even though the actual implementations are named useGetGpaList, usePutVerifyGpa, useGetLanguageTestList, usePutVerifyLanguageTest; update the named exports to re-export using the original hook names (useGetGpaList, useGetLanguageTestList, usePutVerifyGpa, usePutVerifyLanguageTest) so the exported symbols reflect they are hooks and match the implementation identifiers.
🧹 Nitpick comments (18)
apps/university-web/src/apis/chat/putReadChatRoom.ts (1)
17-17: ⚡ Quick win비어 있는
onError가 오류를 조용히 삼키고 있어요.지금의
onError: (error) => {}는 아무 동작도 하지 않으면서 오류를 그대로 묻어버리고, 사용하지 않는error파라미터까지 남겨 lint 경고를 부를 수 있습니다. 핸들러가 정말 필요 없다면 통째로 제거하는 편이 깔끔하고, 디버깅을 위해 흔적을 남기고 싶다면 최소한의 로깅을 넣어주시면 좋겠습니다.♻️ 불필요한 no-op 핸들러 제거 제안
onSuccess: () => { // 채팅방 목록 쿼리를 무효화하여 새로 고침 queryClient.invalidateQueries({ queryKey: [ChatQueryKeys.chatRooms] }); }, - onError: (error) => {}, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/chat/putReadChatRoom.ts` at line 17, The anonymous no-op error handler onError: (error) => {} silently swallows errors and leaves an unused parameter; either remove the onError property entirely from the options passed to the function (if a handler is not required) or replace it with a minimal logger that forwards the error (e.g., call console.error or the module logger) so errors are not swallowed; locate the onError entry in the options object around putReadChatRoom (or the function constructing the request/handler) and apply the change.apps/university-web/src/apis/Auth/postEmailLogin.ts (1)
12-12: 💤 Low value훅 이름이 동작(이메일 로그인)을 명확히 드러내지 않습니다.
파일명은
postEmailLogin인데 훅은usePostEmailAuth로 다소 모호합니다.usePostEmailLogin처럼 의도를 분명히 하면 다른 인증 훅들과의 일관성도 좋아집니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/postEmailLogin.ts` at line 12, Rename the hook function usePostEmailAuth to usePostEmailLogin to match the file intent and improve clarity; update the function declaration (usePostEmailAuth → usePostEmailLogin), any default/named exports that reference usePostEmailAuth, and all call sites/imports that import/use this hook so they reference usePostEmailLogin instead; ensure TypeScript types or JSDoc mentioning usePostEmailAuth are updated as well to avoid unresolved symbol errors.apps/university-web/src/apis/Auth/postAppleAuth.ts (1)
25-27: ⚖️ Poor tradeoff
setTimeout(100)에 의존한 리다이렉트는 타이밍에 취약합니다.Zustand persist가
localStorage에 저장될 시간을 100ms로 가정하는 방식은 느린 기기나 환경에서는 충분하지 않을 수 있어 간헐적 인증 누락을 유발할 수 있습니다. 가능하면 저장 완료 시점을 기다리거나(onRehydrateStorage/명시적 await) 토큰을 인메모리로 우선 사용하는 방식이 더 견고합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/postAppleAuth.ts` around lines 25 - 27, The redirect currently relies on a fixed setTimeout(() => router.push("/"), 100) which is timing-sensitive; replace this by waiting for Zustand persist to finish or using the in-memory token before redirecting: remove the ad-hoc setTimeout and either await the store rehydration (use onRehydrateStorage or the persist promise) or explicitly set the token in-memory in the auth flow and then call router.push("/") once the store rehydrate/assignment completes; update code paths in postAppleAuth.ts around the setTimeout/router.push call to perform the push only after confirming persistence or in-memory token assignment.apps/university-web/src/apis/Auth/postKakaoAuth.ts (1)
24-31: ⚡ Quick win100ms 지연 이동은 타이밍에 기댄 임시방편으로 보여요.
토스트가 보일 시간을 벌려고
setTimeout(100)뒤에 이동시키는 의도는 이해되지만, 이런 매직 딜레이는 기기 성능이나 렌더링 타이밍에 따라 깜빡임이나 토스트 누락으로 이어질 수 있습니다. 토스트 라이브러리의 표시 후 콜백이나duration기반 처리로 바꾸면 의도가 더 명확해집니다. 또한onError에서/login으로만 보내고 사용자에게 실패 이유를 알리지 않는데, 성공 흐름과 대칭으로 에러 토스트를 함께 노출하는 편이 친절합니다.♻️ 제안
onSuccess: (data) => { if (data.isRegistered) { setAccessToken(data.accessToken); - showIconToast("logo", "로그인에 성공했습니다."); - setTimeout(() => { - router.push("/"); - }, 100); + showIconToast("logo", "로그인에 성공했습니다."); + router.push("/"); } else { router.push(`/sign-up?token=${data.signUpToken}`); } }, - onError: () => { - router.push("/login"); - }, + onError: () => { + showIconToast("logo", "로그인에 실패했습니다. 다시 시도해 주세요."); + router.push("/login"); + },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Auth/postKakaoAuth.ts` around lines 24 - 31, Replace the fragile setTimeout navigation in the success branch with a deterministic callback or promise from the toast API: call showIconToast("logo", "로그인에 성공했습니다.") and when that toast's onClose / .then / duration-complete handler fires, call router.push("/"); target the block in postKakaoAuth where showIconToast(...) and setTimeout(...) are used and remove setTimeout. Also mirror success UX in the error path by calling showIconToast or an error toast with the failure message before or while calling router.push("/login") inside the onError branch so the user sees why login failed (update the onError handler that currently only does router.push("/login")).apps/university-web/src/apis/kakao-api/index.ts (1)
2-4: 💤 Low value훅을
getKakaoInfo처럼 동사형 이름으로 재내보내면 헷갈려요 🪪여기서 내보내는
getKakaoInfo/getKakaoUserIds/postKakaoUnlink는 사실useGetKakaoInfo같은 React 훅인데,kakaoApiApi.getKakaoInfo(순수 API 함수)와 이름이 겹쳐 호출부에서 무엇이 훅인지 한눈에 구분하기 어렵습니다.useXxx형태로 재내보내면 훅 규칙(Rules of Hooks)도 명확해지고 혼동도 줄어듭니다.♻️ 이름 정리 예시
export { kakaoApiApi } from "./api"; -export { default as getKakaoInfo } from "./getKakaoInfo"; -export { default as getKakaoUserIds } from "./getKakaoUserIds"; -export { default as postKakaoUnlink } from "./postKakaoUnlink"; +export { default as useGetKakaoInfo } from "./getKakaoInfo"; +export { default as useGetKakaoUserIds } from "./getKakaoUserIds"; +export { default as usePostKakaoUnlink } from "./postKakaoUnlink";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/kakao-api/index.ts` around lines 2 - 4, The exported names currently expose React hooks as verbs (getKakaoInfo, getKakaoUserIds, postKakaoUnlink) which collides with potential plain API functions; rename the re-exports to hook-style names (e.g., useGetKakaoInfo, useGetKakaoUserIds, usePostKakaoUnlink) so callers can immediately recognize they are hooks; update the export statements in this module to export the default from "./getKakaoInfo" as useGetKakaoInfo (and similarly for "./getKakaoUserIds" and "./postKakaoUnlink") and then search/replace their usages across the codebase to import the new hook names.apps/university-web/src/apis/MyPage/patchInterestedRegionCountry.ts (1)
5-9: ⚡ Quick win관심 지역 변경 후 프로필 캐시가 그대로 남습니다.
MenteeInfo에는interestedCountries가 포함되어 있는데, 이 변이는 성공해도QueryKeys.MyPage.profile쿼리를 무효화하지 않아요. 그래서 화면에 이전 관심 국가가 잠시 그대로 보일 수 있습니다.
patchProfile처럼onSettled에서 프로필을 invalidate해 두면 일관성이 유지됩니다.♻️ 무효화 추가 제안
-import { useMutation } from "`@tanstack/react-query`"; +import { useMutation, useQueryClient } from "`@tanstack/react-query`"; import type { AxiosError } from "axios"; +import { QueryKeys } from "../queryKeys"; import { type InterestedRegionCountryRequest, type InterestedRegionCountryResponse, myPageApi } from "./api"; const usePatchInterestedRegionCountry = () => { + const queryClient = useQueryClient(); return useMutation<InterestedRegionCountryResponse, AxiosError, InterestedRegionCountryRequest>({ mutationFn: (data) => myPageApi.patchInterestedRegionCountry(data), + onSettled: () => { + queryClient.invalidateQueries({ queryKey: [QueryKeys.MyPage.profile] }); + }, }); };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/MyPage/patchInterestedRegionCountry.ts` around lines 5 - 9, The mutation usePatchInterestedRegionCountry currently calls myPageApi.patchInterestedRegionCountry but does not invalidate the profile cache, so MenteeInfo.interestedCountries stays stale; update usePatchInterestedRegionCountry to add an onSettled handler that calls queryClient.invalidateQueries(QueryKeys.MyPage.profile) (similar to patchProfile) to ensure the profile query is invalidated after success/failure and the UI reflects updated interestedCountries.apps/university-web/src/apis/applications/api.ts (1)
6-8: ⚡ Quick win1. applications 목록 키를 competitors 의미로 두지 않는 편이 안전합니다.
지금 정의된
competitorsApplicationList가 실제로는 지원 목록 조회 훅에서 사용됩니다. 같은 디렉터리의 경쟁자 조회는 이미QueryKeys.applications.competitors를 쓰고 있어서 applications 도메인 캐시 체계가 둘로 갈리고, 이후 목록 무효화나 갱신을 공용 키 기준으로 걸면 이 쿼리만 빠질 가능성이 큽니다.applicationList처럼 실제 의미에 맞는 키로 바꾸고 공용QueryKeys체계에 맞춰 두는 편이 낫습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/applications/api.ts` around lines 6 - 8, The ApplicationsQueryKeys constant incorrectly names the applications list key as competitorsApplicationList which splits cache keys from the shared QueryKeys.applications namespace; rename the key to applicationList (change the property name competitorsApplicationList -> applicationList and its string value to "applicationList") and update all call sites that reference ApplicationsQueryKeys.competitorsApplicationList to ApplicationsQueryKeys.applicationList (and ensure any invalidations or usages align with QueryKeys.applications.* such as QueryKeys.applications.applicationList) so the applications domain uses a single consistent cache key.apps/university-web/src/apis/community/api.ts (1)
69-82: ⚡ Quick win메서드별 반환 형태가 섞여 있어 호출부에서 혼동이 생길 수 있어요.
getPostList와deletePost는AxiosResponse를 통째로 반환하는데,getBoard·getBoardList·getPostDetail는res.data만 반환합니다. 같은communityApi객체 안에서 규칙이 달라 호출부가.data접근 여부를 매번 기억해야 합니다. 한쪽 컨벤션(보통.data반환)으로 통일하시면 사용 편의성이 좋아집니다.Also applies to: 125-127
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/community/api.ts` around lines 69 - 82, The methods in communityApi currently mix returning raw AxiosResponse (e.g., getPostList, deletePost) and unwrapped res.data (getBoardList, getBoard, getPostDetail), causing caller confusion; update getPostList and deletePost to await the axios call and return res.data to match the other methods (and update their return types to Promise<ListPost[]> / appropriate DTO types), ensuring all communityApi methods consistently return the response body rather than the full AxiosResponse.apps/university-web/src/apis/community/getBoard.ts (1)
3-11: ⚡ Quick win커뮤니티 쿼리 키 소스가 두 군데로 나뉘어 있어요.
이 훅은
QueryKeys.community.board(공통 모듈)를 쓰는데, 같은 도메인의 다른 훅들은./api의CommunityQueryKeys(로컬 상수)를 사용합니다. 키 출처가 이원화되면 무효화 대상이 어긋나 캐시 갱신이 누락될 위험이 있으니, 가능하면 한 소스로 통일하시길 권합니다.또한
enabled: !!boardCode는boardCode가 숫자0일 때 요청이 비활성화되는데, 시그니처가string | number를 허용하므로 경계값에서 의도와 달라질 수 있습니다. 문자열 코드만 받는다면 타입을string으로 좁히는 편이 명확합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/community/getBoard.ts` around lines 3 - 11, The hook useGetBoard currently mixes query key sources (QueryKeys.community.board vs. CommunityQueryKeys in ./api) and uses enabled: !!boardCode which disables requests for numeric 0; fix by choosing a single canonical query-key source (switch useGetBoard to use the local CommunityQueryKeys from ./api or update other hooks to use the shared QueryKeys so all keys align) and clarify the boardCode type/guard (either change the parameter type to string if only string codes are allowed, or change the enabled check to a strict null/undefined check like boardCode != null to allow 0); update references in useGetBoard (queryKey, queryFn, and enabled) to match the chosen key constant and adjusted type/guard.apps/university-web/src/apis/community/deletePost.ts (1)
33-33: ⚡ Quick win빈
catch블록이 오류를 조용히 삼키고 있어요.revalidate 실패를 의도적으로 무시하시는 건 이해되지만, 완전히 빈 블록은 디버깅 단서를 남기지 않고 일부 린터(biome
noEmptyBlockStatements)에서 경고로 잡힐 수 있습니다. 최소한 의도를 드러내는 주석이나 가벼운 로깅을 남겨 두시길 권합니다.♻️ 제안 변경
- } catch (error) {} + } catch (error) { + // ISR revalidate 실패는 사용자 흐름을 막지 않도록 무시 (필요 시 모니터링 로깅 추가) + console.warn("community page revalidate failed", error); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/community/deletePost.ts` at line 33, The empty catch block in the deletePost function is swallowing failures silently; update the catch to either log the caught error (e.g., console.warn or a processLogger.warn) with context like "revalidate failed for post deletion" or add an explicit comment such as "// intentionally ignoring revalidate errors" to satisfy linters and make the intent clear; locate the catch following the revalidate/revalidation call inside deletePost and replace the empty block with a short log statement or explanatory comment that includes the caught error variable.apps/university-web/src/apis/community/postCreatePost.ts (1)
13-27: ⚡ Quick win재검증 실패가 완전히 무음 처리됩니다.
catch (error) {}가 비어 있어,/api/revalidate호출이 실패하면 ISR 페이지가 오래된 상태로 남아도 아무런 신호가 남지 않습니다.게시글 생성 흐름 자체를 막을 필요는 없으니 throw까지는 필요 없지만, 디버깅을 위한 최소한의 로그 한 줄은 남겨 주시는 편이 좋겠습니다.
♻️ 최소 로깅 제안
- } catch (error) {} + } catch (error) { + console.error("[revalidateCommunityPage] revalidate 요청 실패:", error); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/community/postCreatePost.ts` around lines 13 - 27, The catch block swallowing errors for the revalidation fetch must log the failure; update the catch in the async revalidation code that uses accessToken and fetch("/api/revalidate") to at minimum call console.error (or the app's logger) with a short message and the caught error (e.g., "revalidate failed" + error) so failures are visible while still not throwing and blocking the post creation flow.apps/university-web/src/apis/news/postCreateNews.ts (1)
28-35: 💤 Low value1. 낙관적 썸네일의 object URL이 해제되지 않아요.
URL.createObjectURL로 만든 미리보기 URL은onSettled의 재조회로 낙관적 항목이 교체된 뒤에도 메모리에 남습니다. 아티클을 자주 추가하는 흐름이라면URL.revokeObjectURL로 정리해 주면 누수를 막을 수 있어요. (putUpdateNews.ts에도 동일하게 적용됩니다.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/news/postCreateNews.ts` around lines 28 - 35, The optimisticArticle creation uses URL.createObjectURL for the thumbnail but never revokes it, causing memory leaks; capture the created object URL (the value assigned to optimisticArticle.thumbnailUrl) in a local variable when creating optimisticArticle in postCreateNews.ts (and apply the same pattern in putUpdateNews.ts), then ensure you call URL.revokeObjectURL on that variable in the mutation's onSettled/onSuccess handler after the optimistic entry is replaced (or on error), so the temporary object URL is cleaned up; reference optimisticArticle, URL.createObjectURL and the mutation onSettled handler when making the change.apps/university-web/src/apis/news/deleteLikeNews.ts (1)
17-48: ⚡ Quick win1. 서버 응답으로 좋아요 수치를 다시 맞추는 단계가 빠져 있어요.
이 훅은 낙관적 업데이트와
onError롤백까지는 잘 처리하고 있어요. 다만onSettled(또는onSuccess)에서invalidateQueries로 재조회하는 단계가 없어서, 서버가 돌려주는 실제likeCount와 화면에 표시된 값이 미세하게 어긋날 수 있습니다.postLikeNews.ts와도 동일한 상황이니, 두 훅을 함께 맞춰 두면 좋아요/취소를 빠르게 반복할 때 수치 일관성이 더 안정적입니다.♻️ 제안: 정산 단계 추가
onError: (_err, _variables, context) => { if (context?.previousArticleList) { queryClient.setQueryData<ArticleListResponse>(queryKey, context.previousArticleList); } }, + + onSettled: () => { + queryClient.invalidateQueries({ queryKey }); + }, });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/news/deleteLikeNews.ts` around lines 17 - 48, Add a reconciliation step after the optimistic update by calling queryClient.invalidateQueries(queryKey) in the mutation's onSettled (or onSuccess) handler so the UI is refreshed with the server's authoritative likeCount; update the useMutation block in deleteLikeNews (the mutation using mutationFn: newsApi.deleteArticleLike) to include an onSettled that invalidates the same queryKey (mirror the change in postLikeNews.ts as well) to ensure likeCount consistency after the server response.apps/university-web/src/apis/mentor/getReceivedMentorings.ts (1)
5-5: 💤 Low value
OFFSET상수가api.ts와 중복돼요 🔁
api.ts에도 동일한OFFSET = 5가 있어 값이 두 곳에서 따로 관리됩니다. 한쪽만 바뀌면 어긋날 수 있으니, 가능하면api.ts에서 export 해 공유하는 편이 안전합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/mentor/getReceivedMentorings.ts` at line 5, The OFFSET constant is duplicated—remove the local const OFFSET in getReceivedMentorings.ts and instead import the shared OFFSET exported from api.ts; update getReceivedMentorings.ts to import { OFFSET } from the module that defines it (the exported symbol in api.ts) and use that imported OFFSET in any functions (e.g., the function that computes pagination/limit) so the value is maintained in a single place.apps/university-web/src/apis/mentor/postApplyMentoring.ts (1)
13-16: 💤 Low value주석 문구만 살짝 다듬으면 좋겠어요 📝
무효화 대상은
applyMentoringList(신청한 멘토링 목록)인데 주석은 "멘토 목록을 새로고침"이라고 적혀 있어요. 동작 자체는 올바르니, 혼동을 줄이려면 주석을 "신청한 멘토링 목록 새로고침"으로 맞춰 주시면 깔끔합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/mentor/postApplyMentoring.ts` around lines 13 - 16, The comment above the onSuccess handler is misleading; update the comment for the invalidate call that targets MentorQueryKeys.applyMentoringList to accurately say "신청한 멘토링 목록 새로고침" instead of "멘토 목록을 새로고침" so it matches the behavior around queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.applyMentoringList] }) in postApplyMentoring.ts.apps/university-web/src/apis/mentor/api.ts (1)
177-180: 💤 Low value
region값은 URL 인코딩을 권장해요 🔗
region이 쿼리 문자열에 그대로 끼워집니다. 공백이나 비ASCII 지역명(예: 한글)이 들어오면 쿼리가 깨질 수 있으니encodeURIComponent로 감싸 두면 안전합니다.getApplyMentoringList의verifyStatus도 동일하게 점검해 두시면 좋아요.♻️ 제안 diff
- const res = await axiosInstance.get<MentorListResponse>(`/mentors?region=${region}&page=${page}&size=${size}`); + const res = await axiosInstance.get<MentorListResponse>( + `/mentors?region=${encodeURIComponent(region)}&page=${page}&size=${size}`, + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/mentor/api.ts` around lines 177 - 180, The getMentorList function currently inserts region directly into the query string which can break for spaces or non-ASCII characters; update getMentorList to URL-encode region using encodeURIComponent when building `/mentors?region=...&page=...&size=...`, and also check getApplyMentoringList to wrap verifyStatus (the query parameter) with encodeURIComponent in its request building so all query values are safely encoded.apps/university-web/src/apis/mentor/patchConfirmMentoring.ts (1)
11-17: ⚡ Quick win
onSuccess에서Promise.all을 return/await 하지 않고 있어요 ⏳지금은
Promise.all([...])결과가 그대로 버려져서, 무효화가 완료되기 전에 뮤테이션이 먼저 settle 됩니다. 또한 반환·await 되지 않은 프라미스라 실패 시 unhandled rejection이 될 수 있어요.postApplyMentoring처럼async/await로 무효화 완료를 기다리도록 맞추면 동작도 일관되고 안전해집니다. 더불어 변수명queriesClient는queryClient오타로 보여요.♻️ 제안 diff
-const usePatchMentorCheckMentorings = () => { - const queriesClient = useQueryClient(); - return useMutation<PatchCheckMentoringsResponse, AxiosError, PatchCheckMentoringsRequest>({ - onSuccess: () => { - // 멘토링 체크 상태 변경 후 멘토링 목록 쿼리 무효화 - Promise.all([ - queriesClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringList] }), - queriesClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringNewCount] }), - ]); - }, - mutationFn: mentorApi.patchMentorCheckMentorings, - }); -}; +const usePatchMentorCheckMentorings = () => { + const queryClient = useQueryClient(); + return useMutation<PatchCheckMentoringsResponse, AxiosError, PatchCheckMentoringsRequest>({ + onSuccess: async () => { + // 멘토링 체크 상태 변경 후 멘토링 목록 쿼리 무효화 + await Promise.all([ + queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringList] }), + queryClient.invalidateQueries({ queryKey: [MentorQueryKeys.mentoringNewCount] }), + ]); + }, + mutationFn: mentorApi.patchMentorCheckMentorings, + }); +};🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/mentor/patchConfirmMentoring.ts` around lines 11 - 17, onSuccess currently fires Promise.all([...]) without awaiting or returning it and uses the misspelled queriesClient; change the onSuccess handler to be async and await/return the Promise.all that invalidates MentorQueryKeys.mentoringList and MentorQueryKeys.mentoringNewCount so the mutation waits for invalidation to complete (matching postApplyMentoring behavior), and rename queriesClient to the correct queryClient identifier.apps/university-web/src/apis/Scores/api.ts (1)
12-36: 💤 Low value공개 타입 표면의 작은 불일치 두 가지를 정리하면 좋겠습니다.
거의 쌍둥이 같은 두 흐름인데 이름과 타입이 살짝 엇갈려 있어 눈이 잠깐 멈칫합니다.
- 네이밍 규칙
UseMyGpaScoreResponse에는Get이 빠져 있고UseGetMyLanguageTestScoreResponse에는 들어 있어, 둘 중 하나로 통일하면 자동완성에서 나란히 정렬되어 찾기 쉬워집니다.file필드 타입
UsePostGpaScoreRequest.file은Blob,UsePostLanguageTestScoreRequest.file은File로 선언되어 있습니다.File이Blob을 확장하므로 컴파일은 통과하지만, 동일한 의미의 업로드 필드는 같은 타입으로 맞추는 편이 호출부 혼선을 줄여줍니다.♻️ 일관성을 맞추는 제안
-export interface UseMyGpaScoreResponse { +export interface UseGetMyGpaScoreResponse { gpaScoreStatusResponseList: GpaScore[]; } @@ export interface UsePostGpaScoreRequest { gpaScoreRequest: { gpa: number; gpaCriteria: number; issueDate: string; // yyyy-MM-dd }; - file: Blob; + file: File; }
UseMyGpaScoreResponse를 변경하는 경우getGpaList.ts와index.ts의 재-export도 함께 맞춰주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/university-web/src/apis/Scores/api.ts` around lines 12 - 36, Unify the public types: rename UseMyGpaScoreResponse to UseGetMyGpaScoreResponse (to match UseGetMyLanguageTestScoreResponse) and make both POST request file fields the same type by changing UsePostGpaScoreRequest.file from Blob to File (or change both to Blob if you prefer), then update any re-exports/usages (notably in getGpaList.ts and index.ts) to the new name; ensure identifiers UseMyGpaScoreResponse, UseGetMyLanguageTestScoreResponse, UsePostGpaScoreRequest.file, and UsePostLanguageTestScoreRequest.file are updated consistently across the codebase.
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Install pnpm | ||
| uses: pnpm/action-setup@v3 | ||
| with: | ||
| version: 9 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "22.x" | ||
| cache: "pnpm" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 새로 추가된 university-web 잡의 액션 참조와 checkout 옵션을 점검합니다.
sed -n '69,95p;156,187p' .github/workflows/ci.yml
echo
echo '[uses references]'
rg -n 'uses:\s*(actions/checkout|actions/setup-node|pnpm/action-setup)@' .github/workflows/ci.yml
echo
echo '[checkout blocks]'
rg -n -A3 -B1 'name:\s*Checkout repository' .github/workflows/ci.ymlRepository: solid-connection/solid-connect-web
Length of output: 3441
CI 보안 강화: 액션 참조를 SHA로 고정하고 checkout에 persist-credentials: false 추가하기
university-web-quality-check/university-web-build의uses:가actions/checkout@v4,pnpm/action-setup@v3,actions/setup-node@v4처럼 태그 기반이라 공급망 리스크가 커서 커밋 SHA로 고정해 주세요.- 두 잡 모두
Checkout repository에persist-credentials: false가 없어 읽기 전용 체크아웃에서도 불필요한 자격 증명이 유지될 수 있으니 추가해 주세요.
🔧 예시 수정안
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@<PINNED_SHA>
+ with:
+ persist-credentials: false
- name: Install pnpm
- uses: pnpm/action-setup@v3
+ uses: pnpm/action-setup@<PINNED_SHA>
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@<PINNED_SHA>🧰 Tools
🪛 zizmor (1.25.2)
[warning] 76-77: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 77-77: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 80-80: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 85-85: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 76 - 88, Replace the tag-based action
references with pinned commit SHAs for actions/checkout, pnpm/action-setup, and
actions/setup-node in both the university-web-quality-check and
university-web-build jobs to reduce supply-chain risk (e.g., change uses:
actions/checkout@v4 → uses: actions/checkout@<commit-sha>), and add
persist-credentials: false to the Checkout repository step (the actions/checkout
invocation) in both jobs to avoid leaking credentials on read-only checkouts;
update the three uses: lines and the checkout step accordingly wherever they
appear.
| }, | ||
| } as const; | ||
|
|
||
| export type QueryKey = (typeof QueryKeys)[keyof typeof QueryKeys]; |
There was a problem hiding this comment.
1) QueryKey 타입이 리프 문자열 유니온을 표현하지 못합니다
- Line 132의 현재 타입은 `Auth | news | ...` 같은 “객체 유니온”으로 평가되어, 실제 쿼리 키 문자열 타입 안정성이 깨집니다. 리프 값 유니온으로 바꿔야 합니다.
수정 예시(diff)
export type QueryKey = (typeof QueryKeys)[keyof typeof QueryKeys];
+type LeafValues<T> = T extends object ? LeafValues<T[keyof T]> : T;
+export type QueryKey = LeafValues<typeof QueryKeys>;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/university-web/src/apis/queryKeys.ts` at line 132, The current QueryKey
type evaluates to a union of object shapes rather than the leaf string values;
replace the single-line alias with a recursive leaf-extraction type and then set
QueryKey to the extracted leaf type. Concretely, add a generic recursive helper
(e.g., LeafValues<T>) that resolves to T when T is a string and otherwise
recurses into T[keyof T], then change the QueryKey definition to use
LeafValues<typeof QueryKeys> so QueryKey becomes the union of all leaf string
keys from QueryKeys.
|
|
||
| export type AddWishRequest = Record<string, never>; | ||
|
|
||
| export type IsWishResponse = undefined; |
There was a problem hiding this comment.
2. isWish 조회가 상태값을 절대 노출하지 못합니다.
Line 53의 IsWishResponse = undefined 때문에 Line 162의 GET 호출은 200이어도 data가 항상 undefined입니다. 이 상태로는 하트 초기 상태를 안정적으로 렌더링할 수 없으니, 백엔드 계약에 맞는 boolean 또는 명시적 객체 타입으로 바꿔 주세요.
Also applies to: 162-169
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/university-web/src/apis/universities/api.ts` at line 53, Change the
incorrect type alias IsWishResponse (currently set to undefined) to match the
backend contract—either a boolean or an explicit object like { isWish: boolean
}—and update the GET request in this file that uses IsWishResponse so its
generic and any consumers read the real boolean field (e.g., replace usages
expecting data to be undefined and instead read data or data.isWish). Ensure the
exported IsWishResponse type and the fetch call signatures in
apps/university-web/src/apis/universities/api.ts are consistent so the heart
initial state can be reliably derived from the response.
| export type ByRegionCountryResponse = undefined; | ||
|
|
There was a problem hiding this comment.
1. 지역/국가 검색 응답 타입이 undefined로 고정돼 있습니다.
Line 132에서 ByRegionCountryResponse를 undefined로 선언해서, Line 191의 /universities/search 호출은 성공해도 소비자에게 남는 데이터가 없습니다. 검색 결과를 그리는 훅이므로 실제 응답 스키마로 타입을 잡아야 합니다.
Also applies to: 191-193
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/university-web/src/apis/universities/api.ts` around lines 132 - 133,
ByRegionCountryResponse is incorrectly declared as undefined so the
/universities/search consumer gets no typed data; replace that alias with the
actual response shape returned by the search endpoint (e.g., a typed object or
array such as { items: University[]; total: number; page?: number; perPage?:
number } or the existing PaginatedResponse type), update the export of
ByRegionCountryResponse accordingly, and ensure the search caller that consumes
ByRegionCountryResponse (the /universities/search call) and any hooks import the
correct University or paginated response types so the hook can render the
returned data.
| return useMutation<WishResponse, AxiosError, number>({ | ||
| mutationFn: (universityInfoForApplyId) => universitiesApi.deleteWish({ univApplyInfoId: universityInfoForApplyId }), | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] }); | ||
| }, |
There was a problem hiding this comment.
3. 삭제 후 isWish 캐시도 함께 무효화해 주세요.
지금은 위시리스트만 invalidate해서, 같은 학교를 보고 있는 화면의 useGetIsWish 캐시가 남습니다. 삭제 직후에도 하트가 켜진 채 보일 수 있습니다.
수정 예시
return useMutation<WishResponse, AxiosError, number>({
mutationFn: (universityInfoForApplyId) => universitiesApi.deleteWish({ univApplyInfoId: universityInfoForApplyId }),
- onSuccess: () => {
+ onSuccess: (_, univApplyInfoId) => {
queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.wishList] });
+ queryClient.invalidateQueries({ queryKey: [QueryKeys.universities.isWish, univApplyInfoId] });
},
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/university-web/src/apis/universities/deleteWish.ts` around lines 12 -
16, The deleteWish mutation currently only invalidates
QueryKeys.universities.wishList, so the cached "isWish" state (used by
useGetIsWish) remains stale; update the onSuccess handler in the useMutation
returned by deleteWish (mutationFn -> universitiesApi.deleteWish) to also
invalidate the isWish cache—call queryClient.invalidateQueries for
QueryKeys.universities.isWish and include the universityInfoForApplyId
(univApplyInfoId) as the identifier so the per-university isWish query is
refreshed immediately after deletion.
| queryKey: [QueryKeys.universities.wishList], | ||
| queryFn: () => universitiesApi.getWishList({}), | ||
| staleTime: 1000 * 60 * 5, | ||
| select: (data) => data as unknown as ListUniversity[], |
There was a problem hiding this comment.
위시리스트만 응답 전체를 통째로 캐스팅하고 있어요 — 한 번 짚고 넘어가요.
형제 훅들(getSearchFilter, getSearchText)은 data.univApplyInfoPreviews처럼 내부 배열 필드를 꺼내 쓰는데, 이 훅만 data 전체를 ListUniversity[]로 단언합니다. 만약 WishListResponse가 배열을 감싼 래퍼 객체라면, as unknown as가 타입 오류를 가려버린 탓에 런타임에서 배열이 아닌 객체가 흘러가 .map/.length 사용처가 조용히 깨질 수 있습니다. WishListResponse의 실제 형태를 확인해 주세요.
🛡️ 응답이 배열을 감싼 형태일 경우의 예시
- select: (data) => data as unknown as ListUniversity[],
+ select: (data) => data.univApplyInfoPreviews as unknown as ListUniversity[],WishListResponse 타입과 getWishList 응답 구조를 확인합니다.
#!/bin/bash
# WishListResponse 정의와 getWishList 엔드포인트 매핑 확인
fd -t f 'api.ts' apps/university-web/src/apis/universities | xargs -I{} sh -c 'echo "== {} =="; cat "{}"'
rg -nP --type=ts -C3 '\bWishListResponse\b'🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/university-web/src/apis/universities/getWishList.ts` at line 16, The
select handler in getWishList is unsafely casting the entire response (data) to
ListUniversity[] which hides mismatched shapes; inspect the WishListResponse
type and the actual endpoint payload and change the select from "select: (data)
=> data as unknown as ListUniversity[]" to return the proper inner array (e.g.,
data.univApplyInfoPreviews or the actual array field name) and adjust the return
type to ListUniversity[] (remove the as unknown as cast), ensuring getWishList
and any consumers use the correct field and types consistent with
WishListResponse.
…-deploy chore: 대학 정보 릴리즈 타깃 추가
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Line 12: The workflow default for workflow_dispatch.inputs.target is set to
"all", which causes releases to include release-university when left
unspecified; change the workflow to remove or narrow that default (e.g., set to
an explicit subset or no default) and update any calling scripts/docs to always
pass the intended target; inspect and update references to
workflow_dispatch.inputs.target and any invocations (manual dispatch, automation
scripts) to ensure they explicitly provide the desired target values so
university is not promoted unintentionally.
🪄 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: CHILL
Plan: Pro
Run ID: e2593a98-d552-48f3-8d20-4b94699eb7b9
📒 Files selected for processing (1)
.github/workflows/release.yml
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 845b8921f8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const languageTestTypeParam = searchParams.get("languageTestType"); | ||
| const queryLanguageTestType = isLanguageTestType(languageTestTypeParam) ? languageTestTypeParam : null; | ||
| const queryCountryCodesKey = searchParams.getAll("countryCode").filter(isCountryCode).join(","); | ||
| const queryRegion = searchParams.getAll("region").filter(isRegionFilterValue).at(0) ?? RegionEnumExtend.ALL; |
There was a problem hiding this comment.
Preserve all selected region filters
When users submit the advanced school search with multiple regions selected, buildUniversitySearchQuery appends every selected region, but this page collapses searchParams.getAll("region") to only .at(0). As a result /university/inha?region=유럽권®ion=미주권 shows only the first region instead of the union of selected regions, which regresses the multi-region filter behavior that this form still exposes.
Useful? React with 👍 / 👎.
관련 이슈
작업 내용
apps/university-web별도 Next.js zone으로 분리했습니다.apps/web에는/university/score/*,/university/application/*만 남기고, catalog route는UNIVERSITY_WEB_DOMAIN기반 rewrite로 연결했습니다.apps/university-web에assetPrefix: "/university-static", SSG routerevalidate = false,dynamicParams = false를 적용했습니다.university-web검증/빌드 경로를 추가했습니다.apps/web에서 대학 정보 catalog 이동은<a>를 사용하도록 lint 규칙을 추가했습니다.빌드타임 개선 측정
비교 기준
Web - Buildjob은 약 40 ~ 48초, 실제Build web application단계는 약 25~27초였습니다.Generating static pages using 3 workers (188/188) in 5.6s로, 대학 상세 SSG가 web build 안에서 함께 수행됐습니다.26805266005의Web - Buildjob79021184400기준입니다.개선 결과
Build web application단계Web - Buildjob해석
/university/score/*,/university/application/*만 남고, 대학 정보 catalog SSG는apps/university-web에서 별도로 생성됩니다.apps/university-web은Generating static pages using 3 workers (158/158) in 3.3s로 catalog SSG를 유지합니다. 즉, SEO/GEO를 위한 정적 페이지 생성은 유지하되 main web 배포와 분리했습니다.특이 사항
UNIVERSITY_WEB_DOMAIN이 필수입니다. 누락 시/universitycatalog가 404가 되므로next.config.mjs에서 의도적으로 build fail 처리했습니다.apps/university-web를 별도 Project로 만들고 Root Directory를apps/university-web로 설정해야 합니다./university/list/:homeUniversity는 child route를 유지하지 않고/university/:homeUniversitySSG 페이지로 rewrite합니다.robots.txt에서Disallow: /로 차단합니다.검증
pnpm install --frozen-lockfilepnpm --filter @solid-connect/web run lint:checkpnpm --filter @solid-connect/university-web run lint:checkpnpm --filter @solid-connect/web run typecheckpnpm --filter @solid-connect/university-web run typecheckUNIVERSITY_WEB_DOMAIN=https://solid-connect-university-web.vercel.app pnpm --filter @solid-connect/web run buildpnpm --filter @solid-connect/university-web run buildenv -u UNIVERSITY_WEB_DOMAIN pnpm --filter @solid-connect/web run build실패 확인node scripts/check-university-zone-navigation.mjspnpm exec biome check scripts/check-university-zone-navigation.mjs리뷰 요구사항 (선택)
apps/web/next.config.mjs의 rewrite 범위가 의도한 catalog route만 잡는지 확인 부탁드립니다.