fix: 영상 업로드 한도를 화면이 먼저 알려 주고 먼저 막는다
"저장할 수 없는 크기"의 출처는 dev API 앞단 nginx다. `client_max_body_size`가 기본값 1MiB라 그보다 큰 본문을 봉투 없이 413으로 끊는다(실측: 1,000KB→401 통과 / 1,050KB→413 nginx HTML). 영상은 사실상 항상 이 선을 넘어 브라우저 경로로는 올릴 수 없었다. 인프라를 백엔드 `maxUploadSize`와 같은 100m로 올리기로 해(사용자 확정) 그 값을 기준으로 `MAX_STREAM_UPLOAD_BYTES`를 둔다. 화면 쪽 문제도 함께 고친다 — 지금까지 영상 칸은 확장자만 안내하고 크기는 말하지 않았다. 사용자는 한도를 모른 채 고르고, 업로드가 끝나서야 사유 없는 실패를 봤다. 이제 「mp4 · 100MB 이하」를 입력칸 밑에 적고, 넘는 파일은 고르는 순간 "영상은 100MB 이하만 올릴 수 있습니다."로 막는다. 확장자 검사가 먼저라 mov를 고르면 확장자 사유가 뜬다. 한도와 안내 문구는 `formatMaxUploadSize`로 한 값에서 만든다 — 둘이 어긋나면 안내가 거짓말이 된다. ⚠️ 인프라 반영 전에는 이 상한을 통과한 파일도 413으로 끊긴다. 반영 여부는 업로드 엔드포인트에 1MiB 넘는 본문을 보내 401(통과)인지 413(차단)인지로 확인한다. 확인: 안내 문구 "mp4 · 100MB 이하", 150MB → "영상은 100MB 이하만 올릴 수 있습니다.", mov → "mp4 파일만 올릴 수 있습니다.", 두 경우 모두 업로드가 시작되지 않고 videoFileId가 빈 값. Co-Authored-By: Claude Opus 5
@59af56846f67caae39600d212982079628610489
--- app/(protected)/(basic)/contents/_components/content-video-field.tsx
+++ app/(protected)/(basic)/contents/_components/content-video-field.tsx
... | ... | @@ -5,12 +5,18 @@ |
| 5 | 5 |
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
|
| 6 | 6 |
import {
|
| 7 | 7 |
FILE_MODULE, |
| 8 |
+ MAX_STREAM_UPLOAD_BYTES, |
|
| 8 | 9 |
allowedExtensions, |
| 9 | 10 |
fileModuleAccept, |
| 11 |
+ formatMaxUploadSize, |
|
| 10 | 12 |
isAllowedFileExtension, |
| 11 | 13 |
} from '@/lib/domain/file-module'; |
| 12 | 14 |
|
| 13 | 15 |
const UPLOAD_ENDPOINT = `/contents/upload?module=${FILE_MODULE.vodContent.id}`;
|
| 16 |
+ |
|
| 17 |
+const MAX_SIZE_TEXT = formatMaxUploadSize(MAX_STREAM_UPLOAD_BYTES); |
|
| 18 |
+ |
|
| 19 |
+const UPLOAD_HELP_TEXT = `${allowedExtensions(FILE_MODULE.vodContent).join(', ')} · ${MAX_SIZE_TEXT} 이하`;
|
|
| 14 | 20 |
|
| 15 | 21 |
interface ContentVideoFieldProps {
|
| 16 | 22 |
defaultFileId?: string | null; |
... | ... | @@ -42,6 +48,12 @@ |
| 42 | 48 |
); |
| 43 | 49 |
return; |
| 44 | 50 |
} |
| 51 |
+ // 넘겨 보내면 앞단이 사유 없는 413으로 끊는다 — 고르는 순간 이유를 말해 준다. |
|
| 52 |
+ if (file.size > MAX_STREAM_UPLOAD_BYTES) {
|
|
| 53 |
+ setError(`영상은 ${MAX_SIZE_TEXT} 이하만 올릴 수 있습니다.`);
|
|
| 54 |
+ return; |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 45 | 57 |
const body = new FormData(); |
| 46 | 58 |
body.set('file', file);
|
| 47 | 59 |
|
... | ... | @@ -68,6 +80,7 @@ |
| 68 | 80 |
accept={fileModuleAccept(FILE_MODULE.vodContent)}
|
| 69 | 81 |
selectLabel="파일선택" |
| 70 | 82 |
placeholder="파일을 선택해 주세요." |
| 83 |
+ description={UPLOAD_HELP_TEXT}
|
|
| 71 | 84 |
disabled={isUploading}
|
| 72 | 85 |
onSelect={upload}
|
| 73 | 86 |
onRemove={() => setFileId('')}
|
--- lib/domain/file-module.ts
+++ lib/domain/file-module.ts
... | ... | @@ -126,3 +126,21 @@ |
| 126 | 126 |
* 없다. 더 큰 파일은 미들웨어에서 제외한 스트리밍 라우트(`/contents/upload`)로 보낸다. |
| 127 | 127 |
*/ |
| 128 | 128 |
export const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024; |
| 129 |
+ |
|
| 130 |
+/** |
|
| 131 |
+ * 스트리밍 업로드(`/contents/upload`)로 올릴 수 있는 상한 — 백엔드 |
|
| 132 |
+ * `multipartResolver.maxUploadSize`(100,000,000)와 같은 값이다. |
|
| 133 |
+ * |
|
| 134 |
+ * 화면이 파일을 고르는 순간 이 값으로 먼저 막는다. 넘겨 보내면 앞단이 사유 없는 413으로 |
|
| 135 |
+ * 끊어 사용자가 무엇이 잘못됐는지 알 수 없다. |
|
| 136 |
+ * |
|
| 137 |
+ * 이슈: dev API 앞단 nginx의 `client_max_body_size`가 기본값 1MiB라 실제로는 1MiB만 통과했다 |
|
| 138 |
+ * (2026-08-21 실측: 1,000KB→401 / 1,050KB→413). 인프라를 백엔드와 같은 100m로 올리기로 해 |
|
| 139 |
+ * (사용자 확정) 이 값을 기준으로 둔다. 인프라 반영 전에는 이 상한을 통과해도 413이 온다. |
|
| 140 |
+ */ |
|
| 141 |
+export const MAX_STREAM_UPLOAD_BYTES = 100 * 1000 * 1000; |
|
| 142 |
+ |
|
| 143 |
+/** "100MB"처럼 사람이 읽는 표기. 상한과 안내 문구가 어긋나지 않게 한 곳에서 만든다. */ |
|
| 144 |
+export function formatMaxUploadSize(bytes: number): string {
|
|
| 145 |
+ return `${Math.floor(bytes / (1000 * 1000))}MB`;
|
|
| 146 |
+} |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?