[DEV] API Refactor#30
Conversation
…es to resolve build/render issues
…stead of direct CDN calls
There was a problem hiding this comment.
Code Review
This pull request refactors the post and image fetching architecture by introducing internal API routes (/getPostList, /getPostData, /getPostImage) to handle CDN communication, updating frontend components to point to these new endpoints, and adding an INTERNAL_API_URL environment variable. Feedback focuses on addressing an architectural anti-pattern where Server Components make internal HTTP requests to their own API routes, which can degrade performance and reliability. Additionally, it is recommended to centralize the duplicated isSafeInput function, use absolute URLs for OpenGraph metadata images to prevent broken previews, and switch the post list retrieval endpoint from POST to GET to follow RESTful practices and enable caching.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const getBaseUrl = () => { | ||
| if (typeof window !== "undefined") return ""; // client-side 상대 경로 사용 | ||
| return process.env.INTERNAL_API_URL || "http://localhost:3000"; // server-side 로컬 API 타겟 | ||
| }; |
There was a problem hiding this comment.
⚠️ 아키텍처 및 성능 우려: Server Component에서의 자체 API 호출 (안티패턴)
현재 리팩토링에서는 PostDataUtil.ts가 직접 CDN을 바라보지 않고, Next.js의 자체 API 엔드포인트(/getPostData, /getPostList 등)를 HTTP fetch를 통해 호출하도록 변경되었습니다.
하지만 Server Component(서버 사이드 렌더링)에서 동일한 Next.js 서버의 API 라우트를 HTTP로 호출하는 것은 Next.js 공식 문서에서도 권장하지 않는 안티패턴입니다.
발생할 수 있는 문제점:
- 성능 저하 (Latency): 동일한 서버 내부에서 불필요한 HTTP 요청/응답 오버헤드(TCP 핸드셰이크, 네트워크 홉, JSON 직렬화/역직렬화)가 추가되어 SSR/SSG 페이지 렌더링 속도가 느려집니다.
- 환경 변수 의존성 및 배포 취약성: 서버 사이드에서 로컬 API를 호출하기 위해
INTERNAL_API_URL과 같은 절대 경로 환경 변수가 필수적입니다. 배포 환경(Vercel, Docker 등)이나 포트 변경 시 오동작할 위험이 큽니다. - 서버 리소스 고갈: 트래픽이 몰릴 경우, 서버가 자기 자신에게 HTTP 요청을 보내고 대기하는 과정에서 커넥션 풀 고갈이나 데드락(Deadlock)이 발생할 수 있습니다.
권장하는 해결 방안:
비즈니스 로직(CDN에서 데이터를 가져오고 가공하는 로직)을 공통 헬퍼 함수로 분리하고, Server Component와 API 라우트가 각각 이 함수를 직접 호출하도록 구조를 변경해야 합니다.
| function isSafeInput(input: string | null): boolean { | ||
| if (!input) return false; | ||
| if (input.includes("/") || input.includes("\\") || input.includes("..")) { | ||
| return false; | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
| if (type === "solving") { | ||
| if (id.startsWith("boj")) { | ||
| imageUrl = "/api/getPostImage?postType=solving&postID=dummy&srcID=thumb_boj.png"; | ||
| imageUrl = "/getPostImage?postType=solving&postID=dummy&srcID=thumb_boj.png"; | ||
| } else if (id.startsWith("programmers")) { | ||
| imageUrl = "/api/getPostImage?postType=solving&postID=dummy&srcID=thumb_programmers.png"; | ||
| imageUrl = "/getPostImage?postType=solving&postID=dummy&srcID=thumb_programmers.png"; | ||
| } | ||
| } else { | ||
| const imageFolder = type === "about" ? id : PostURL; | ||
| imageUrl = `/api/getPostImage?postType=${type}&postID=${imageFolder}&srcID=post.png`; | ||
| imageUrl = `/getPostImage?postType=${type}&postID=${imageFolder}&srcID=post.png`; | ||
| } |
There was a problem hiding this comment.
🌐 OpenGraph 이미지 절대 경로 사용 권장
OpenGraph 및 Twitter 카드용 이미지 URL(imageUrl)로 상대 경로(/getPostImage...)를 사용하고 있습니다.
Next.js에서는 메타데이터의 이미지 URL이 상대 경로일 경우, metadataBase가 설정되어 있지 않으면 프로덕션 환경에서 절대 경로로 올바르게 변환되지 않아 카카오톡, 디스코드, 트위터 등에서 미리보기 이미지가 깨질 수 있습니다.
안전한 메타데이터 제공을 위해 SITE_URL을 활용하여 절대 경로로 지정하는 것을 권장합니다.
| if (type === "solving") { | |
| if (id.startsWith("boj")) { | |
| imageUrl = "/api/getPostImage?postType=solving&postID=dummy&srcID=thumb_boj.png"; | |
| imageUrl = "/getPostImage?postType=solving&postID=dummy&srcID=thumb_boj.png"; | |
| } else if (id.startsWith("programmers")) { | |
| imageUrl = "/api/getPostImage?postType=solving&postID=dummy&srcID=thumb_programmers.png"; | |
| imageUrl = "/getPostImage?postType=solving&postID=dummy&srcID=thumb_programmers.png"; | |
| } | |
| } else { | |
| const imageFolder = type === "about" ? id : PostURL; | |
| imageUrl = `/api/getPostImage?postType=${type}&postID=${imageFolder}&srcID=post.png`; | |
| imageUrl = `/getPostImage?postType=${type}&postID=${imageFolder}&srcID=post.png`; | |
| } | |
| if (type === "solving") { | |
| if (id.startsWith("boj")) { | |
| imageUrl = `${SITE_URL}/getPostImage?postType=solving&postID=dummy&srcID=thumb_boj.png`; | |
| } else if (id.startsWith("programmers")) { | |
| imageUrl = `${SITE_URL}/getPostImage?postType=solving&postID=dummy&srcID=thumb_programmers.png`; | |
| } | |
| } else { | |
| const imageFolder = type === "about" ? id : PostURL; | |
| imageUrl = `${SITE_URL}/getPostImage?postType=${type}&postID=${imageFolder}&srcID=post.png`; | |
| } |
| export async function POST(req: NextRequest) { | ||
| try { | ||
| const body = await req.json(); | ||
| const { postType } = body; |
There was a problem hiding this comment.
🌐 RESTful API 디자인: 조회(Read) 작업에 GET 메서드 사용 권장
현재 포스트 목록을 가져오는 API가 POST 메서드로 구현되어 있습니다.
단순히 데이터를 조회(Read)하는 작업은 HTTP GET 메서드를 사용하고, 필요한 파라미터(postType)는 쿼리 스트링(Query String)으로 전달하는 것이 RESTful API 설계 원칙에 부합합니다.
GET 메서드를 사용할 경우 다음과 같은 장점이 있습니다:
- 브라우저 및 CDN 캐싱 활용 가능:
POST요청은 기본적으로 캐싱되지 않지만,GET요청은 브라우저나 CDN 레벨에서 손쉽게 캐싱할 수 있어 성능을 크게 향상시킬 수 있습니다. - 디버깅 용이성: 브라우저 주소창에 직접 URL을 입력하여 API 응답을 즉시 확인할 수 있습니다.
There was a problem hiding this comment.
Pull request overview
자체 API 엔드포인트(/getPostData, /getPostList, /getPostImage)를 신설/정리하여, 게시글/목록/이미지 데이터를 Firebase 대신 jsDelivr CDN 기반으로 가져오고(Front Matter 포함 원문 마크다운 유지), 프론트 유틸(PostDataUtil.ts)은 CDN 직접 접근 대신 내부 API 호출로 통일한 리팩토링입니다.
Changes:
/getPostData,/getPostList라우트 핸들러를 추가해 CDN의posts.json및post.md를 백엔드 명세 형태로 래핑 응답PostDataUtil.ts의 데이터/이미지 접근을 내부 API 호출로 전환하고, 서버 컴포넌트용 절대 URL 바인딩(getBaseUrl) 추가- 이미지 요청 경로를
/api/getPostImage→/getPostImage로 변경하고, 레거시/api/getPostData라우트 제거
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/PostDataUtil.ts | CDN 직접 fetch 제거 후 내부 API 호출로 통일, SSR용 base URL 처리 및 Front Matter strip 적용 |
| src/app/getPostList/route.ts | CDN posts.json 기반 목록 API 신규 추가 |
| src/app/getPostData/route.ts | CDN posts.json + post.md 기반 단일 포스트 API 신규 추가 |
| src/app/getPostImage/route.ts | 이미지 fetch를 CDN 직접 호출 방식으로 정리(POST: base64 JSON, GET: 바이너리 응답 유지) |
| src/app/api/getPostData/route.ts | 레거시 /api/getPostData 라우트 제거 |
| src/app/[type]/[id]/page.tsx | OG 이미지 URL을 /getPostImage로 변경 |
| src/app/[type]/_component/PostCard/PostCardDesktop.tsx | 카드 썸네일 URL을 /getPostImage로 변경 |
| src/app/[type]/_component/MDRender/MDRender.tsx | 마크다운 이미지 URL을 /getPostImage로 변경 |
| .env.example | SSR 내부 API 호출용 INTERNAL_API_URL 예시 추가 및 설명 정리 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| export const CDN_BASE_URL = "https://cdn.jsdelivr.net/gh/yymin1022/Blog_LR_Data@master"; | ||
| export const SITE_URL = process.env.URL_PUB || "https://dev-lr.com"; | ||
| export const SITE_URL = process.env.URL_PUB || "http://localhost:3000"; |
| const getBaseUrl = () => { | ||
| if (typeof window !== "undefined") return ""; // client-side 상대 경로 사용 | ||
| return process.env.INTERNAL_API_URL || "http://localhost:3000"; // server-side 로컬 API 타겟 | ||
| }; |
| const postsIndex = await indexResponse.json() as Record<string, PostData[]>; | ||
| const categoryPosts = postsIndex && Array.isArray(postsIndex[postType]) ? postsIndex[postType] : []; |
| const postsIndex = await indexResponse.json() as Record<string, PostData[]>; | ||
| const categoryPosts = postsIndex && Array.isArray(postsIndex[postType]) ? postsIndex[postType] : []; | ||
| const post = categoryPosts.find((p) => p.postID === postID); |
Summary
Blog_LR 내 자체 API 엔드포인트를 리팩토링 하였습니다.
Description
/getPostData,/getPostImage,/getPostListNext.js 루트 API 라우트가 Firebase 대신 원격 jsDelivr CDN 레포지토리와 연동하여 데이터를 가져오고 기존 백엔드 명세와 동일하게 호환되는 결과물(마크다운 Front Matter 보존 등)을 응답하도록 구현했습니다.PostDataUtil.ts의 헬퍼 함수들이 직접 CDN을 바라보지 않고, Next.js 자체 API 엔드포인트를 전적으로 호출하도록 리팩토링했습니다.getBaseUrl절대경로 바인딩을 적용했습니다.PostDataUtil.ts에서 최종stripFrontMatter가공 후 컴포넌트에 반환하게 하여 화면 렌더링 호환성을 확보했습니다.page.tsx,MDRender.tsx,PostCardDesktop.tsx에서 이미지 호출 대상을 새로운 자체 백엔드 엔드포인트/getPostImage로 변경했습니다.