임동욱 임동욱 08-19
fix: 큰 첨부파일이 서버에서 잡히지 않는 오류로 끝나던 문제
## 확인한 것

임시 Server Action에 파일을 크기별로 보내 재현했다.

- 100KB · 9MB → 정상 도달
- 10.5MB · 12MB · 21MB → 500 `Unexpected end of form`

21MB에서도 "Body exceeded 20mb limit."이 아니라 같은 오류가 나므로 `bodySizeLimit`의 판정이
아니다 — Next 16.2.12(Turbopack) dev에서 Server Action의 multipart 본문이 **10MB 부근에서
잘리고**, 그러면 액션 본문이 실행되지 않아 서버에서 손쓸 방법이 없다.

## 고침

막을 수 있는 곳이 브라우저뿐이라 첨부 input에서 크기를 먼저 잰다. 상한을 넘으면 선택을 비우고
문구를 띄운다 — 종전에는 그대로 전송돼 처리 불가한 500으로 끝났다.

`bodySizeLimit`은 20mb로 올려 두었다. 이번 절단과는 무관하지만, 앱 상한(첨부 10MB)과 같은 값이면
초과 파일이 우리 검증 대신 전송 단계에서 먼저 잘려 "10MB 이하만" 문구를 영영 보여줄 수 없다.

Co-Authored-By: Claude Opus 5 
@fc6e1268c9146c1f97925c3ae7e6758d4f56e82c
app/(protected)/(basic)/boards/_components/board-attachment-field.tsx
--- app/(protected)/(basic)/boards/_components/board-attachment-field.tsx
+++ app/(protected)/(basic)/boards/_components/board-attachment-field.tsx
@@ -1,6 +1,9 @@
 'use client';
 
+import { useState } from 'react';
+import { FoxHelperText } from '@fox/core/components/fox-helper-text';
 import { Field } from '@/components/ui/field';
+import { MAX_ATTACHMENT_BYTES } from '@/lib/data/repositories/file-repository';
 
 interface BoardAttachmentFieldProps {
   label?: string;
@@ -23,10 +26,16 @@
  * 새 파일을 고르면 기존 첨부는 교체된다(백엔드 필드가 `atchFileId` 하나뿐이다). 파일을 고르지
  * 않으면 기존 첨부가 그대로 유지된다.
  */
+const OVERSIZE_MESSAGE = `첨부파일은 ${Math.floor(
+  MAX_ATTACHMENT_BYTES / (1024 * 1024)
+)}MB 이하만 올릴 수 있습니다.`;
+
 export function BoardAttachmentField({
   label = '첨부파일',
   attachmentId,
 }: BoardAttachmentFieldProps) {
+  const [error, setError] = useState<string | undefined>(undefined);
+
   return (
     <Field label={label}>
       {/* 기존 첨부 id를 함께 제출해 "파일을 안 골랐으면 유지"를 서버가 판단할 수 있게 한다. */}
@@ -45,11 +54,23 @@
         </p>
       ) : null}
 
+      {/* 이슈: 상한을 넘는 파일은 Server Action 본문이 잘려 500으로 끝나고 액션이 실행되지
+          않는다. 서버에서 잡을 수 없어 브라우저에서 미리 막는다. */}
       <input
         type="file"
         name="attachment"
         className="w-full text-body-sm text-foreground file:mr-3 file:rounded-md file:border file:border-border file:bg-surface file:px-3 file:py-1.5 file:text-body-sm file:text-foreground"
+        onChange={(event) => {
+          const file = event.target.files?.[0];
+          if (file && file.size > MAX_ATTACHMENT_BYTES) {
+            event.target.value = '';
+            setError(OVERSIZE_MESSAGE);
+            return;
+          }
+          setError(undefined);
+        }}
       />
+      {error && <FoxHelperText type="danger" message={error} />}
     </Field>
   );
 }
next.config.ts
--- next.config.ts
+++ next.config.ts
@@ -22,7 +22,9 @@
 const nextConfig: NextConfig = {
   experimental: {
     serverActions: {
-      bodySizeLimit: '10mb',
+      // 이슈: 앱의 파일 상한(첨부 10MB)보다 반드시 커야 한다. 같거나 작으면 초과 파일이 413으로
+      // 잘려 액션이 실행되지 않고, 우리가 준비한 "10MB 이하만" 메시지 대신 처리 불가한 오류가 뜬다.
+      bodySizeLimit: '20mb',
     },
   },
 
Add a comment
List