Next.js 환경에서 Supabase 의 스토리지를 연결하여 파일업로드를 하는 작업을 진행하고 있었다. 하지만 계속 400 BadRequest 가 떠서 Supabase 공식문서 및 여러 곳에서 검색을 해보았지만 명확한 원인을 찾지 못했다…🙁
파일을 선택한 후 “파일업로드” 버튼을 누르면 Supabase 의 스토리지에 저장되도록 로직을 짜놓은 상태
storageActions.ts
'use server';
import { createServerSupabaseClient } from 'utils/supabase/server';
function handleError(error: Error) {
if (error) {
console.error(error);
throw error;
}
}
export async function uploadFile(formData: FormData) {
const supabase = await createServerSupabaseClient();
const file = formData.get('file') as File;
const { data, error } = await supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET)
.upload(file.name, file, { upsert: true });
handleError(error);
return data;
}
AddFileZone.tsx
'use client';
import React, { useRef } from 'react';
import * as S from './styled';
import Button from '@/app/_modules/common/components/button/button/Button';
import { uploadFile } from 'actions/storageActions';
const AddFileZone = () => {
const fileRef = useRef<HTMLInputElement>(null);
return (
<S.AddFileZone
onSubmit={async (e) => {
e.preventDefault();
const file = fileRef.current?.files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
await uploadFile(formData);
}
}}
>
<input type='file' ref={fileRef} />
<Button
type='submit'
text='파일 업로드'
iconName='plus'
filled
/>
</S.AddFileZone>
);
};
export default AddFileZone;
서버액션 파일에서 uploadFile 함수를 가져와 선택된 file 을 인수로 넣어 작동하게끔 해놓았다.
하지만 계속 에러가 뜨는 상황이었다.
결국 AI 를 통해그 원인을 찾아보니, Next.js 에서는 서버액션을 할 때 FormData 형식의 데이터를 받는데 제한이 있다고 한다. 그래서 아래와 같이 수정을 했다.
AddFileZone.tsx
'use client';
import React, { useRef } from 'react';
import * as S from './styled';
import Button from '@/app/_modules/common/components/button/button/Button';
const AddFileZone = () => {
const fileRef = useRef<HTMLInputElement>(null);
// API Route로 파일 업로드
const handleUpload = async (formData: FormData) => {
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const result = await res.json();
if (!res.ok) throw new Error(result.error || 'Upload failed');
return result.data;
};
return (
<S.AddFileZone
onSubmit={async (e) => {
e.preventDefault();
const file = fileRef.current?.files?.[0];
if (file) {
const formData = new FormData();
formData.append('file', file);
try {
const result = await handleUpload(formData);
console.log(result);
} catch (err) {
alert((err as Error).message);
}
}
}}
>
<input type='file' ref={fileRef} />
<Button type='submit' text='파일 업로드' iconName='plus' filled />
</S.AddFileZone>
);
};
export default AddFileZone;
서버액션인 uploadFile 을 무시하고, 따로 API Router 를 통해 클라이언트 요청을 진행했다.
route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createServerSupabaseClient } from 'utils/supabase/server';
export async function POST(req: NextRequest) {
try {
const formData = await req.formData();
const file = formData.get('file') as File | null;
if (!file) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
}
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET!)
.upload(file.name, file, { upsert: true }); // safeName을 key로 사용
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ data });
} catch (err: any) {
return NextResponse.json({ error: err.message || 'Unknown error' }, { status: 500 });
}
}
이렇게 했더니 잘 동작하는 모습을 볼 수 있었다..!
하지만 파일 형식의 데이터가 왜 서버액션에서 지원이 안되는걸까 ㅠㅠ