Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough엽서 이미지 URL을 정리한 Changes
Sequence Diagram(s)sequenceDiagram
participant Client as 브라우저 (PostCardActions)
participant Server as Next.js API (/api/file)
participant Remote as 외부 파일 서버
rect rgb(220,245,255)
Client->>Server: GET /api/file?url=<fileUrl>
note right of Server: 서버에서 외부 리소스 fetch 및 변환
Server->>Remote: fetch(<fileUrl>)
Remote-->>Server: Response (binary, content-type)
Server->>Server: response.blob() → arrayBuffer → number[]
Server-->>Client: 200 { type, arrayBuffer }
end
rect rgb(235,255,230)
Client->>Client: JSON → Blob 생성\nObjectURL 생성 → anchor.click() → 다운로드 시작
Client->>Client: URL.revokeObjectURL()
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
시
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🏷️ Labeler has automatically applied labels based on your PR title, branch name, or commit message. |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
src/pages/main/node/[placeId].tsx (1)
1-1: Pages Router에서 'use client' 지시어 제거 필요이 프로젝트는 Next.js Pages Router를 사용하고 있습니다. 'use client' 지시어는 App Router 전용이므로 제거해야 합니다.
Based on learnings
다음 diff를 적용하세요:
-'use client'; import { useState, useEffect } from 'react';src/shared/components/main/components/board/Boardgame.tsx (1)
1-1: Pages Router에서 'use client' 지시어 제거 필요이 프로젝트는 Next.js Pages Router를 사용하고 있으므로 'use client' 지시어를 제거해야 합니다.
Based on learnings
-'use client'; import Image from 'next/image';src/pages/mypage/index.tsx (1)
1-1: Pages Router에서 'use client' 지시어 제거 필요이 프로젝트는 Next.js Pages Router를 사용하고 있으므로 'use client' 지시어를 제거해야 합니다.
Based on learnings
-'use client'; - import React, { useEffect } from 'react';src/pages/main/PostCard.tsx (1)
1-1: Pages Router에서 'use client' 지시어 제거 필요이 프로젝트는 Next.js Pages Router를 사용하고 있으므로 'use client' 지시어를 제거해야 합니다.
Based on learnings
-'use client'; import { useEffect, useState } from 'react';src/pages/mypage/postcard/[id].tsx (1)
1-1: Pages Router에서 'use client' 지시어 제거 필요이 프로젝트는 Next.js Pages Router를 사용하고 있으므로 'use client' 지시어를 제거해야 합니다.
Based on learnings
-'use client'; import { Header, FlipCard, LocationCard } from '@/shared/components';
🧹 Nitpick comments (1)
src/pages/main/node/[placeId].tsx (1)
129-139: 주요 이미지에서 priority 속성 제거 재고 필요히어로 이미지에서
priority속성을 제거하면 LCP(Largest Contentful Paint) 성능에 부정적인 영향을 줄 수 있습니다. 이 이미지가 above-the-fold 콘텐츠인 경우 우선순위 로딩이 권장됩니다.성능 최적화를 위해
priority속성을 다시 추가하는 것을 고려하세요:<Image src={imageUrl || '/assets/board.svg'} alt={placeName} fill + priority onLoadingComplete={() => setImageLoaded(true)}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
public/assets/background.pngis excluded by!**/*.pngsrc/shared/icons/source/Stamp.svgis excluded by!**/*.svg
📒 Files selected for processing (9)
src/pages/main/PostCard.tsx(2 hunks)src/pages/main/node/[placeId].tsx(1 hunks)src/pages/map/index.tsx(2 hunks)src/pages/map/location/[placeId].tsx(1 hunks)src/pages/mypage/index.tsx(1 hunks)src/pages/mypage/postcard/[id].tsx(2 hunks)src/shared/components/main/components/PostCardActions/PostCardActions.tsx(1 hunks)src/shared/components/main/components/board/Boardgame.tsx(2 hunks)src/shared/icons/components/icon.tsx(0 hunks)
💤 Files with no reviewable changes (1)
- src/shared/icons/components/icon.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/map/index.tsx
🧬 Code graph analysis (4)
src/shared/components/main/components/board/Boardgame.tsx (2)
src/shared/components/main/components/stampBoard/Stamp.tsx (1)
StampProps(10-35)src/pages/main/index.tsx (1)
MainPage(8-68)
src/pages/mypage/postcard/[id].tsx (2)
src/shared/api/member.ts (1)
useGetPostCardDetail(45-51)src/shared/components/main/components/PostCardActions/PostCardActions.tsx (1)
PostCardActions(10-25)
src/pages/map/index.tsx (2)
src/shared/hooks/useCourseSelection.ts (1)
useCourseSelection(3-16)src/shared/components/header/ControlBar.tsx (1)
ControlBar(27-75)
src/pages/map/location/[placeId].tsx (1)
src/shared/lib/utils.ts (1)
cn(71-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
🔇 Additional comments (8)
src/pages/main/node/[placeId].tsx (1)
123-123: 히어로 이미지 높이 대폭 증가 검토 필요이미지 컨테이너 높이가 256px에서 43rem(약 688px)으로 크게 증가했습니다. 모바일 뷰포트에서 이미지가 화면을 과도하게 차지하여 주요 콘텐츠가 첫 화면에서 밀려날 수 있습니다.
다양한 기기에서 레이아웃을 테스트하여 사용자 경험이 적절한지 확인해 주세요.
src/shared/components/main/components/board/Boardgame.tsx (2)
27-27: 배경 이미지 형식 변경으로 인한 파일 크기 증가 확인 필요배경 이미지가 SVG에서 PNG로 변경되었습니다. PNG 파일이 일반적으로 SVG보다 크므로 번들 크기와 로딩 성능에 영향을 줄 수 있습니다.
PNG 파일 크기를 확인하고 성능 영향을 검토해 주세요. 가능하다면 최적화된 WebP 형식도 고려할 수 있습니다.
65-65: 아이콘 색상 변경 확인Stamp 아이콘 색상이 'white'에서 'gray-50'으로 변경되었습니다. 이는 IconColor 타입에서 'white' 옵션이 제거된 것과 일치합니다.
src/pages/map/index.tsx (1)
16-17: 일관된 코드 스타일 적용 확인따옴표 스타일을 작은따옴표로 통일하고 패딩 값을 조정한 포맷팅 변경사항입니다. 코드베이스 전반의 일관성을 향상시킵니다.
Also applies to: 50-79
src/pages/map/location/[placeId].tsx (1)
34-51: 접근성 및 사용자 경험 개선이미지 섹션에
aria-label을 추가하고 불투명도 전환 효과를 구현하여 접근성과 사용자 경험이 개선되었습니다.src/shared/components/main/components/PostCardActions/PostCardActions.tsx (1)
3-23: 공유 기능 제거 확인PostCardActions 컴포넌트에서 공유(Export) 버튼이 제거되고 저장(Save) 기능만 남았습니다. 이것이 의도된 디자인 변경인지 확인해 주세요.
이 변경으로 인해 사용자가 엽서를 공유할 수 있는 다른 방법이 제공되는지 확인해 주세요.
src/pages/main/PostCard.tsx (1)
19-19: 기본 이미지 및 주석 업데이트 확인기본 이미지 경로가 일관되게 업데이트되었고, 주석이 변경된 기능(공유 버튼 제거)을 정확하게 반영합니다.
Also applies to: 71-71
src/pages/mypage/postcard/[id].tsx (1)
7-7: PostCardActions 통합 및 기본 이미지 업데이트 확인PostCardActions 컴포넌트가 올바르게 통합되었고, 기본 이미지가 다른 엽서 관련 페이지와 일관되게 업데이트되었습니다.
Also applies to: 15-15, 68-68
| <section | ||
| aria-label='저장한 엽서' | ||
| className='w-full mt-[1.8rem] relative z-[100]' | ||
| className='w-full mt-[1.8rem] relative z-[10]' |
There was a problem hiding this comment.
🧩 Analysis chain
z-index 감소로 인한 레이어링 충돌 가능성 확인 필요
z-index가 100에서 10으로 크게 감소했습니다. 다른 UI 요소(예: 팝업, 모달, BottomNav 등)가 z-index 10 이상을 사용하는 경우 저장한 엽서 섹션이 가려질 수 있습니다.
다음 스크립트로 z-index 사용 현황을 확인해 주세요:
🏁 Script executed:
#!/bin/bash
# z-index 값이 10 이상인 요소들을 검색하여 충돌 가능성 확인
rg -n "z-\[([1-9]\d+|[2-9]\d)\]" --type tsx --type ts -C 2Length of output: 27
🏁 Script executed:
#!/bin/bash
# 검색: tsx/ts/css 파일에서 z-index 사용 현황 확인
# Tailwind z-index 클래스 및 스타일 속성 검색
echo "=== Tailwind z-index 클래스 검색 (z-[숫자] 형식) ==="
rg -n "z-\[[0-9]+\]" -g "*.tsx" -g "*.ts" -g "*.css" -C 1
echo ""
echo "=== z-index CSS 속성 및 스타일 검색 ==="
rg -n "z-index\s*[:=]" -g "*.tsx" -g "*.ts" -g "*.css" -C 1
echo ""
echo "=== 특히 z-index가 10 이상인 값 검색 ==="
rg -n "z-\[([1-9][0-9]|[0-9]{3,})\]" -g "*.tsx" -g "*.ts" -C 1Length of output: 3939
z-index 감소로 인한 레이어링 충돌 위험 확인됨
z-index를 100에서 10으로 감소시킨 것이 실제로 문제를 야기할 수 있습니다. 코드베이스 전체에서 다음 UI 요소들이 더 높은 z-index를 사용하고 있습니다:
- PopupSet (z-[100], fixed)
- Header (z-[100], fixed)
- Overlay (z-[99], fixed)
- LocationBubbleSet (z-[50], fixed)
저장한 엽서 섹션이 relative z-[10]으로 설정되어 있으므로, 이들 fixed 요소(특히 z-[100]인 Popup과 Header)가 표시될 때 가려질 수 있습니다. z-index를 적절히 조정하거나 레이어링 구조를 재검토하는 것을 권장합니다.
🤖 Prompt for AI Agents
In src/pages/mypage/index.tsx around line 131, the saved-postcard section is set
to className='w-full mt-[1.8rem] relative z-[10]' which is likely to be visually
obscured by fixed elements using higher z-indexes (PopupSet z-[100], Header
z-[100], Overlay z-[99], LocationBubbleSet z-[50]); update the z-index to sit
above those fixed elements or adjust stacking context: either raise the z value
to >=101 (e.g., z-[101]) if this section must overlay fixed elements, or
refactor so the fixed elements are behind (lower their z) or move the
saved-postcard out of a competing stacking context (avoid creating unwanted
stacking contexts on parents); after change, verify visually that PopupSet,
Header, Overlay, and LocationBubbleSet still behave as intended across
breakpoints.
| @@ -1,6 +1,6 @@ | |||
| 'use client'; | |||
There was a problem hiding this comment.
Pages Router에서 'use client' 지시어 제거 필요
이 프로젝트는 Next.js Pages Router를 사용하고 있으므로 'use client' 지시어를 제거해야 합니다.
Based on learnings
-'use client';
import { Icon } from '@/shared/icons';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'use client'; | |
| import { Icon } from '@/shared/icons'; |
🤖 Prompt for AI Agents
In src/shared/components/main/components/PostCardActions/PostCardActions.tsx
lines 1-1, remove the top-level 'use client' directive; ensure the file does not
rely on client-only hooks or browser APIs (useState, useEffect, window,
document). If it currently uses client features, move those bits into a separate
client-only child component or lift state/handlers to a parent client component,
then export PostCardActions as a server component without the directive; run the
app/build to verify no client-only usage errors remain.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/pages/api/file.ts (1)
9-11: 응답 타입에서any사용을 제거하세요.
NextApiResponse<FileResponse | any>에서any를 사용하면 타입 안전성이 저하됩니다. 오류 응답에 대한 명시적 타입을 정의하세요.다음 diff를 적용하세요:
+interface ErrorResponse { + error: string; +} + export default async function handler( req: NextApiRequest, - res: NextApiResponse<FileResponse | any>, + res: NextApiResponse<FileResponse | ErrorResponse>, ) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/pages/api/file.ts(1 hunks)src/shared/components/main/components/PostCardActions/PostCardActions.tsx(2 hunks)src/shared/utils/postcardActions.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/shared/components/main/components/PostCardActions/PostCardActions.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
🔇 Additional comments (1)
src/shared/utils/postcardActions.ts (1)
32-52: 대용량 파일 처리 시 성능 및 메모리 문제를 검토하세요.현재 구현은 전체 파일을 JSON 배열로 직렬화/역직렬화하므로 대용량 파일(예: 수 MB 이상)의 경우 메모리 사용량이 크고 성능이 저하될 수 있습니다.
/api/file엔드포인트가 바이너리 스트림을 직접 프록시하도록 변경하는 것을 고려해보세요.다운로드 대상 파일의 예상 크기를 확인하고, 대용량 파일(>5MB)을 처리해야 하는 경우 더 효율적인 스트리밍 방식으로 리팩토링을 권장합니다.
| const blob = await response.blob(); | ||
| const buffer = await blob.arrayBuffer(); | ||
|
|
||
| res.status(200).send({ | ||
| type: blob.type, | ||
| arrayBuffer: Array.from(new Uint8Array(buffer)), | ||
| }); |
There was a problem hiding this comment.
메모리 고갈 및 성능 저하 위험이 있습니다.
전체 파일을 메모리에 로드하고 JSON 배열로 직렬화하므로 대용량 파일 다운로드 시 서버 메모리가 고갈되고 응답 시간이 크게 증가합니다. 또한 Content-Length 제한이 없어 악의적인 요청으로 서버가 다운될 수 있습니다.
다음과 같이 스트리밍 방식으로 리팩토링하는 것을 권장합니다:
// 파일 크기 제한 (예: 10MB)
const MAX_FILE_SIZE = 10 * 1024 * 1024;
const response = await fetch(urlObj.toString());
if (!response.ok) {
return res.status(500).json({ error: 'Failed to fetch file' });
}
const contentLength = response.headers.get('content-length');
if (contentLength && parseInt(contentLength) > MAX_FILE_SIZE) {
return res.status(413).json({ error: 'File too large' });
}
// 바이너리 스트림으로 직접 전달
res.setHeader('Content-Type', response.headers.get('content-type') || 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment');
const buffer = await response.arrayBuffer();
res.status(200).send(Buffer.from(buffer));그리고 클라이언트 측 downloadFromServer 함수도 바이너리 응답을 처리하도록 수정:
export async function downloadFromServer(url: string, fileName: string) {
try {
const resp = await fetch(`/api/file?url=${encodeURIComponent(url)}`);
if (!resp.ok) {
throw new Error(`파일 다운로드 실패: ${resp.status}`);
}
const blob = await resp.blob();
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = fileName;
a.click();
setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
} catch (err) {
console.error('다운로드 실패:', err);
}
}🤖 Prompt for AI Agents
In src/pages/api/file.ts around lines 27 to 33, the handler loads the entire
fetched file into memory and serializes it as a JSON array (Uint8Array ->
Array.from), risking memory exhaustion and performance degradation for large or
malicious files; change to a streaming/binary response: enforce a max file size
by checking Content-Length header and return 413 if exceeded, validate
response.ok and return 500 on fetch failure, forward content-type and
content-disposition headers, and pipe or send the binary body directly (as a
Buffer/stream) instead of converting to JSON so the server does not buffer the
whole file in memory and the client can receive a proper Blob for download.
| const resp = await fetch(`/api/file?url=${encodeURIComponent(url)}`); | ||
| const json = await resp.json(); |
There was a problem hiding this comment.
응답 상태 검증이 누락되었습니다.
resp.json()을 호출하기 전에 응답 상태를 확인해야 합니다. API 요청이 실패한 경우 JSON 파싱 오류가 발생할 수 있습니다.
다음 diff를 적용하여 응답 상태를 검증하세요:
const resp = await fetch(`/api/file?url=${encodeURIComponent(url)}`);
+ if (!resp.ok) {
+ throw new Error(`파일 다운로드 실패: ${resp.status}`);
+ }
const json = await resp.json();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const resp = await fetch(`/api/file?url=${encodeURIComponent(url)}`); | |
| const json = await resp.json(); | |
| const resp = await fetch(`/api/file?url=${encodeURIComponent(url)}`); | |
| if (!resp.ok) { | |
| throw new Error(`파일 다운로드 실패: ${resp.status}`); | |
| } | |
| const json = await resp.json(); |
🤖 Prompt for AI Agents
In src/shared/utils/postcardActions.ts around lines 34-35, the code calls
resp.json() without verifying the HTTP response status; add a response status
check (e.g., if (!resp.ok) { const errText = await resp.text(); throw new
Error(`Fetch /api/file failed: ${resp.status} ${resp.statusText} - ${errText}`);
}) before calling resp.json() so failed responses are handled and do not cause
JSON parse errors.
| const blobUrl = window.URL.createObjectURL(blob); | ||
|
|
||
| const a = document.createElement('a'); | ||
| a.href = blobUrl; | ||
| a.download = fileName; | ||
| a.click(); | ||
|
|
||
| URL.revokeObjectURL(blobUrl); |
There was a problem hiding this comment.
URL 해제 타이밍 문제 가능성이 있습니다.
a.click() 직후 즉시 URL.revokeObjectURL()을 호출하면, 브라우저가 다운로드를 시작하기 전에 URL이 해제되어 다운로드가 실패할 수 있습니다. 특히 대용량 파일의 경우 이 문제가 발생할 가능성이 높습니다.
다음 diff를 적용하여 약간의 지연 후 URL을 해제하세요:
a.click();
- URL.revokeObjectURL(blobUrl);
+ setTimeout(() => URL.revokeObjectURL(blobUrl), 100);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const blobUrl = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = blobUrl; | |
| a.download = fileName; | |
| a.click(); | |
| URL.revokeObjectURL(blobUrl); | |
| const blobUrl = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = blobUrl; | |
| a.download = fileName; | |
| a.click(); | |
| setTimeout(() => URL.revokeObjectURL(blobUrl), 100); |
🤖 Prompt for AI Agents
In src/shared/utils/postcardActions.ts around lines 41 to 48, revokeObjectURL is
called immediately after a.click(), which can cancel the download before the
browser starts fetching the blob; add a short delay before calling
URL.revokeObjectURL (for example wrap revokeObjectURL in setTimeout with a small
timeout like 500–1000ms) or revoke the URL in a safe callback (e.g., after
navigation/focus or using a.download link click completion handler) so the
download has time to start before the blob URL is released.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/pages/main/PostCard.tsx (1)
68-82: 이미지 표시와 다운로드에 동일한 cleanedImageUrl을 사용하는 점이 좋습니다
FlipCard의frontSrc와PostCardActions의imageUrl모두cleanedImageUrl를 공유하도록 정리한 부분은, 화면에 보이는 이미지와 실제 저장되는 파일이 일치하도록 해줘서 일관성이 좋습니다.위에서 제안한
cleanedImageUrl보정만 반영되면, 표시/다운로드 경로 모두 한 번에 안정성이 올라갈 것 같습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/pages/main/PostCard.tsx(4 hunks)src/pages/mypage/postcard/[id].tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/mypage/postcard/[id].tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/main/PostCard.tsx (2)
src/shared/hooks/useImageOrientation.ts (1)
useImageOrientation(4-29)src/shared/components/main/components/PostCardActions/PostCardActions.tsx (1)
PostCardActions(10-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
🔇 Additional comments (1)
src/pages/main/PostCard.tsx (1)
34-34: null 가드 이후 구조 분해 패턴이 안전합니다
if (!postcard) return으로 null 가드를 한 뒤const { placeName, description, address } = postcard;를 수행하는 패턴은 타입·런타임 모두 안전하고, 읽기도 좋습니다. 현재 형태 그대로 유지해도 괜찮아 보입니다.
| // 🔥 postcard가 null이어도 안전하게 처리 | ||
| const cleanedImageUrl = ( | ||
| postcard?.imageUrl || '/assets/letter_card_backv.png' | ||
| ).trim(); | ||
|
|
||
| // 🔥 훅은 조건문 밖에서 항상 호출! | ||
| const { orientation } = useImageOrientation(cleanedImageUrl); | ||
|
|
There was a problem hiding this comment.
공백만 있는 imageUrl에서 폴백이 적용되지 않을 수 있습니다
(postcard?.imageUrl || '/assets/letter_card_backv.png').trim() 패턴에서는 imageUrl이 ' ' 같은 공백 문자열일 때 문제가 생깁니다.
' '는 truthy라서||에서 그대로 선택되고,trim()이후에는''가 되어useImageOrientation('')→ 훅은 아무 것도 하지 않고orientation은 기본값square유지FlipCard의frontSrc=''→ 깨진 이미지PostCardActions의downloadFromServer('', ...)→ 다운로드 실패 가능
공백·빈 문자열 모두에 대해 폴백이 동작하도록, 먼저 trim()으로 정리한 뒤 폴백을 적용하는 쪽이 안전합니다.
- const cleanedImageUrl = (
- postcard?.imageUrl || '/assets/letter_card_backv.png'
- ).trim();
+ const rawImageUrl = postcard?.imageUrl?.trim();
+ const cleanedImageUrl =
+ rawImageUrl && rawImageUrl.length > 0
+ ? rawImageUrl
+ : '/assets/letter_card_backv.png';이렇게 하면 실제 URL이 공백/빈 문자열인 경우에도 항상 폴백 이미지를 사용하게 되어, 보기/저장 둘 다 안정적으로 동작합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // 🔥 postcard가 null이어도 안전하게 처리 | |
| const cleanedImageUrl = ( | |
| postcard?.imageUrl || '/assets/letter_card_backv.png' | |
| ).trim(); | |
| // 🔥 훅은 조건문 밖에서 항상 호출! | |
| const { orientation } = useImageOrientation(cleanedImageUrl); | |
| // 🔥 postcard가 null이어도 안전하게 처리 | |
| const rawImageUrl = postcard?.imageUrl?.trim(); | |
| const cleanedImageUrl = | |
| rawImageUrl && rawImageUrl.length > 0 | |
| ? rawImageUrl | |
| : '/assets/letter_card_backv.png'; | |
| // 🔥 훅은 조건문 밖에서 항상 호출! | |
| const { orientation } = useImageOrientation(cleanedImageUrl); |
🤖 Prompt for AI Agents
In src/pages/main/PostCard.tsx around lines 18 to 25, the current expression
(postcard?.imageUrl || '/assets/letter_card_backv.png').trim() fails to fall
back when imageUrl is whitespace-only; instead, first normalize by trimming the
possibly undefined value and then apply the fallback so whitespace becomes empty
and triggers the default. Change to trim the source (e.g. (postcard?.imageUrl ??
'').trim()) and then use || '/assets/letter_card_backv.png' to produce
cleanedImageUrl, keeping the useImageOrientation(cleanedImageUrl) hook call
outside any conditional.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/pages/api/file.ts (2)
18-24: 중요: SSRF 취약점이 여전히 존재합니다.이전 리뷰에서 지적된 SSRF(Server-Side Request Forgery) 취약점이 수정되지 않았습니다. 클라이언트가 제공한 임의의 URL로 서버가 요청을 수행하므로, 공격자가 내부 네트워크 리소스나 메타데이터 서비스에 접근할 수 있습니다.
허용된 도메인 화이트리스트를 적용하여 신뢰할 수 있는 호스트(예: S3 버킷)만 접근 가능하도록 제한해야 합니다. 이전 리뷰 코멘트를 참고하세요.
30-36: 중요: 메모리 고갈 위험이 여전히 존재합니다.이전 리뷰에서 지적된 메모리 문제가 수정되지 않았습니다. 전체 파일을 메모리에 로드하고 JSON 배열로 변환하므로 대용량 파일 다운로드 시 서버 메모리가 고갈될 수 있습니다.
스트리밍 방식으로 리팩토링하고 파일 크기 제한을 추가해야 합니다. 이전 리뷰 코멘트의 제안된 구현을 참고하세요.
🧹 Nitpick comments (1)
src/pages/api/file.ts (1)
13-41: 인증 및 요청 제한 추가를 고려하세요.현재 API 엔드포인트는 인증 없이 누구나 접근할 수 있습니다. 프록시로 악용되거나 과도한 요청으로 서버 리소스가 소진될 수 있습니다.
다음 개선 사항을 고려하세요:
- 인증 추가: 세션 또는 API 키 검증
- Rate Limiting: Next.js middleware 또는 라이브러리(예:
@upstash/ratelimit) 사용- 모니터링: 접근하는 URL 로깅으로 의심스러운 활동 추적
예시 (rate limiting with middleware):
// middleware.ts 또는 API 핸들러 내부 import { Ratelimit } from '@upstash/ratelimit'; import { Redis } from '@upstash/redis'; const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '1 m'), // 분당 10회 }); // handler 내부에서 const identifier = req.headers['x-forwarded-for'] || 'anonymous'; const { success } = await ratelimit.limit(identifier); if (!success) { return res.status(429).json({ error: 'Too many requests' }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/pages/api/file.ts(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
| export default async function handler( | ||
| req: NextApiRequest, | ||
| res: NextApiResponse<FileResponse | ErrorResponse>, | ||
| ) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
HTTP 메서드 제한을 추가하세요.
현재 핸들러는 모든 HTTP 메서드(GET, POST, PUT 등)를 허용합니다. 읽기 전용 프록시 엔드포인트이므로 GET 메서드만 허용해야 합니다.
다음 diff를 적용하여 GET 메서드만 허용하도록 제한하세요:
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<FileResponse | ErrorResponse>,
) {
try {
+ if (req.method !== 'GET') {
+ return res.status(405).json({ error: 'Method not allowed' });
+ }
+
const { url } = req.query;🤖 Prompt for AI Agents
In src/pages/api/file.ts around lines 13 to 16, the API handler currently
accepts all HTTP methods; restrict it to only allow GET by checking req.method
at the start of the handler and returning a 405 Method Not Allowed (with an
Allow: GET header) for any non-GET requests, then continue existing logic only
for GET; implement the method check as the first operation inside the exported
async handler so the endpoint behaves as a read-only proxy.
| return res.status(400).json({ error: 'Missing url' }); | ||
| } | ||
|
|
||
| const response = await fetch(url.trim()); |
There was a problem hiding this comment.
fetch 요청에 타임아웃을 추가하세요.
원격 서버가 응답하지 않을 경우 요청이 무한정 대기할 수 있습니다. 이는 서버 리소스 고갈로 이어질 수 있습니다.
다음 diff를 적용하여 타임아웃을 추가하세요:
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10000); // 10초 타임아웃
+
- const response = await fetch(url.trim());
+ try {
+ const response = await fetch(url.trim(), { signal: controller.signal });
+ clearTimeout(timeoutId);
+ } catch (err) {
+ clearTimeout(timeoutId);
+ if (err instanceof Error && err.name === 'AbortError') {
+ return res.status(504).json({ error: 'Request timeout' });
+ }
+ throw err;
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/pages/api/file.ts around line 24, the fetch call uses await
fetch(url.trim()) without a timeout, which can hang indefinitely; add an
AbortController, start a timer (e.g., setTimeout) that calls controller.abort()
after a reasonable timeout (e.g., 5-30s), pass { signal: controller.signal }
into fetch, clear the timeout when fetch resolves, and handle AbortError to
return a proper timeout response or error; ensure URL is still trimmed and
errors are propagated/logged appropriately.
🔥 작업 내용
🤔 추후 작업 사항
🔗 이슈
PR Point (To Reviewer)
📸 피그마 스크린샷 or 기능 GIF
(작업 내역 스크린샷)
Summary by CodeRabbit
Style
New Features
Bug Fixes
Style
New Features
Bug Fixes