임동욱 임동욱 08-21
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
+++ app/(protected)/(basic)/contents/_components/content-video-field.tsx
@@ -5,12 +5,18 @@
 import { FoxHelperText } from '@fox/core/components/fox-helper-text';
 import {
   FILE_MODULE,
+  MAX_STREAM_UPLOAD_BYTES,
   allowedExtensions,
   fileModuleAccept,
+  formatMaxUploadSize,
   isAllowedFileExtension,
 } from '@/lib/domain/file-module';
 
 const UPLOAD_ENDPOINT = `/contents/upload?module=${FILE_MODULE.vodContent.id}`;
+
+const MAX_SIZE_TEXT = formatMaxUploadSize(MAX_STREAM_UPLOAD_BYTES);
+
+const UPLOAD_HELP_TEXT = `${allowedExtensions(FILE_MODULE.vodContent).join(', ')} · ${MAX_SIZE_TEXT} 이하`;
 
 interface ContentVideoFieldProps {
   defaultFileId?: string | null;
@@ -42,6 +48,12 @@
       );
       return;
     }
+    // 넘겨 보내면 앞단이 사유 없는 413으로 끊는다 — 고르는 순간 이유를 말해 준다.
+    if (file.size > MAX_STREAM_UPLOAD_BYTES) {
+      setError(`영상은 ${MAX_SIZE_TEXT} 이하만 올릴 수 있습니다.`);
+      return;
+    }
+
     const body = new FormData();
     body.set('file', file);
 
@@ -68,6 +80,7 @@
         accept={fileModuleAccept(FILE_MODULE.vodContent)}
         selectLabel="파일선택"
         placeholder="파일을 선택해 주세요."
+        description={UPLOAD_HELP_TEXT}
         disabled={isUploading}
         onSelect={upload}
         onRemove={() => setFileId('')}
lib/domain/file-module.ts
--- lib/domain/file-module.ts
+++ lib/domain/file-module.ts
@@ -126,3 +126,21 @@
  *   없다. 더 큰 파일은 미들웨어에서 제외한 스트리밍 라우트(`/contents/upload`)로 보낸다.
  */
 export const MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
+
+/**
+ * 스트리밍 업로드(`/contents/upload`)로 올릴 수 있는 상한 — 백엔드
+ * `multipartResolver.maxUploadSize`(100,000,000)와 같은 값이다.
+ *
+ * 화면이 파일을 고르는 순간 이 값으로 먼저 막는다. 넘겨 보내면 앞단이 사유 없는 413으로
+ * 끊어 사용자가 무엇이 잘못됐는지 알 수 없다.
+ *
+ * 이슈: dev API 앞단 nginx의 `client_max_body_size`가 기본값 1MiB라 실제로는 1MiB만 통과했다
+ *   (2026-08-21 실측: 1,000KB→401 / 1,050KB→413). 인프라를 백엔드와 같은 100m로 올리기로 해
+ *   (사용자 확정) 이 값을 기준으로 둔다. 인프라 반영 전에는 이 상한을 통과해도 413이 온다.
+ */
+export const MAX_STREAM_UPLOAD_BYTES = 100 * 1000 * 1000;
+
+/** "100MB"처럼 사람이 읽는 표기. 상한과 안내 문구가 어긋나지 않게 한 곳에서 만든다. */
+export function formatMaxUploadSize(bytes: number): string {
+  return `${Math.floor(bytes / (1000 * 1000))}MB`;
+}
Add a comment
List