
인프런 워밍업 클럽 3기 풀스택 - 2주차 발자국
2주차 학습 내용
Part 1 - Git Repository 생성 및 초기 설정 진행
create-next-app
을 통해 초기 세팅을 했으며,이전에 TODO에서 했던 코드들을 일부 가져와서 빠르게 세팅함.
Part 2 - UI 작업
알게된 사실 - page.tsx에는 클라이언트 컴포넌트를 사용하면 좋지않다.
그 이유는 나중에 메타데이터를 쓰고 하는데 그건 서버 컴포넌트에서만 돌아가기 때문에 피해줘야 한다.
나는 평소 flex만 사용하고 grid는 잘 사용하지 않는다. 항상 쓰던것만 써서 그렇기도 하고 grid로 편하게 구현하는 것도 flex로 구현은 가능하기 때문에 그렇게 했다.
강의에서는 grid를 사용했고 디테일하진 않지만 간단하게 3단계로 반응형도 쉽게 구현되는 모습을 보고 grid를 다시보게 됨
className="grid md:grid-cols-3 lg:grid-cols-4 grid-cols-2"
그 이후로는 컴포넌트 별로 분리해서 퍼블리싱 작업을 구현했다.
Part 3 - 파일 업로드 구현(Supabase Storage)
사진을 업로드 하는데 알 수 없는 에러가 있었고, 분명 코드도 다른부분이 없는데 문제가 생겨서 오랫동안 붙잡고 있었다.
원인은 사진이름이 한글로된 경우 안되는 부분이였고, 잠시 오류 수정으로 고쳐서 한글이름으로 된 사진도 업로드가 가능하게 했다. 하지만 한글이름이 아닌 a__a__a같은 이름으로 저장되는 문제가 발생해서 이 문제는 추후 고쳐봐야 할 문제 같다.
// actions/storageActions.ts
function sanitizeFileName(fileName: string) {
return fileName
.normalize("NFKD") // 유니코드 정규화
.replace(/[^\w.-]/g, "_") // 특수 문자 제거
.replace(/\s+/g, "_") // 공백을 `_`로 변경
.toLowerCase(); // 소문자로 변환
}
export async function uploadFile(formData: FormData) {
const supabase = await createServerSupabaseClient();
const file = formData.get("file") as File | null;
if (!file) {
console.error("❌ 업로드할 파일이 없습니다.");
throw new Error("파일이 없습니다.");
}
// 파일 이름을 안전한 형식으로 변환
const safeFileName = sanitizeFileName(file.name);
console.log("✅ 변환된 파일 이름:", safeFileName);
const { data, error } = await supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET!)
.upload(safeFileName, file, { upsert: true });
if (error) {
console.error("❌ Supabase 업로드 실패:", error.message);
throw new Error(error.message);
}
return data;
}
Part 4 - 파일제거 구현, Darg & Drop, 멀티파일 업로드 구현
Darg & Drop을 위해 설치해줌.
npm i --save react-dropzone
이번주 미션 - 파일의 마지막 수정(업로드) 시간을 표시하는 기능을 추가하기!
// components/dropbox-image.tsx
<div>생성일: {formatDate(image.updated_at)}</div>
간단하게 이 부분 넣어서 해결함.
미션 이외로...
- 강사님과 같은 코드로 하니까 생긴 에러가 있어서 코드를 약간 수정함.
수정한 부분은
actions/storageActions.ts
의 uploadFile 부분임
1⃣ 파일 필터링 (undefined
값 제거)
✅ 첫 번째 코드 (위 코드) → undefined
또는 잘못된 파일 제거
const files = Array.from(formData.entries())
.map(([name, file]) => file as File)
.filter((file) => file instanceof File && file.name); // ✅ undefined 제거
filter()
를 사용하여undefined
또는 비정상적인 파일을 제거파일이
null
이거나undefined
면upload()
에서 에러 발생 가능성이 있음 → 이를 방지
❌ 두 번째 코드 (아래 코드) → 필터링 없음
const files = Array.from(formData.entries()).map(([name, file]) => file as File);
undefined
파일이 포함될 가능성이 있음 → 업로드 시 오류 발생 가능
2⃣ 파일명 변환 (sanitizeFileName
)
✅ 첫 번째 코드 (위 코드) → 파일명 변환 추가
function sanitizeFileName(fileName: string) {
return fileName
.normalize("NFC") // ✅ 한글 깨짐 방지
.replace(/[^a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣_.-]/g, "_"); // ✅ 특수 문자 제거
}
const safeFileName = sanitizeFileName(file.name);
특수 문자, 공백 제거 (
file.name
을 정리)한글 깨짐 방지 (
NFC
정규화)
→ Supabase는 일부 특수 문자나 공백이 포함된 파일명을 허용하지 않으므로 안정적
❌ 두 번째 코드 (아래 코드) → 원본 파일명 그대로 사용
supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET)
.upload(file.name, file, { upsert: true });
파일명을 그대로 사용하기 때문에, 특수 문자나 공백이 포함되면 Supabase에서 오류 발생 가능
3⃣ async
처리 및 오류 핸들링
✅ 첫 번째 코드 (위 코드) → async
사용 및 오류 처리
const results = await Promise.all(
files.map(async (file) => { // ✅ async 사용
const safeFileName = sanitizeFileName(file.name);
const { data, error } = await supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET)
.upload(safeFileName, file, { upsert: true });
if (error) { // ✅ 오류 처리
console.error("❌ Supabase 업로드 실패:", error.message);
throw new Error(error.message);
}
return data;
})
);
각 파일 업로드가 비동기(async)로 처리됨
오류 발생 시
console.error
로 출력하고 예외 처리각 파일 업로드 후 결과(
data
) 반환
❌ 두 번째 코드 (아래 코드) → 오류 핸들링 없음
const results = await Promise.all(
files.map((file) =>
supabase.storage
.from(process.env.NEXT_PUBLIC_STORAGE_BUCKET)
.upload(file.name, file, { upsert: true })
)
);
async
키워드 없이 바로upload()
실행오류가 발생해도 catch되지 않으며, 전체 업로드가 실패할 가능성이 있음
업로드 성공 여부를 확인할 방법 없음 (
data
를 반환하지 않음)
2주차 회고
2주차에는 중간점검을 하는 시간을 가졌다. 수강생들이 하고 싶었던 질문을 하나하나 답변해주시는 시간을 가져서 꽤 유용한 시간이였고, 더 열심히 하자는 마음을 다지는 계기가 되었다.
첫주때보단 수퍼베이스에 적응을 하는거같다. 아직 친해지기에는 시간이 더 많이 필요할꺼 같긴한데 정처기 준비하고 CS 스터디 하고, 다른 프젝도 마무리 하고, 매일 알고리즘 문제도 풀고 있다보니 시간이 많이 부족한 것 같다.
중간점검때 강사님께서 시간관리에 대한 얘기도 했었는데, 매우 동감하는 부분...
시간 관리나 스케줄 관리를 잘 해야 할 것 같다.. 의욕만 앞서서 살짝 망하는거 같기도함.
그래도 뭐 흥미있고 재미있으니까 만족한다.
댓글을 작성해보세요.