🌱앱 식물 키우기 오픈!

인프런 워밍업 클럽 3기 풀스택 - 2주차 발자국

인프런 워밍업 클럽 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)

 image사진을 업로드 하는데 알 수 없는 에러가 있었고, 분명 코드도 다른부분이 없는데 문제가 생겨서 오랫동안 붙잡고 있었다.

원인은 사진이름이 한글로된 경우 안되는 부분이였고, 잠시 오류 수정으로 고쳐서 한글이름으로 된 사진도 업로드가 가능하게 했다. 하지만 한글이름이 아닌 a__a__a같은 이름으로 저장되는 문제가 발생해서 이 문제는 추후 고쳐봐야 할 문제 같다.image

// 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이거나 undefinedupload()에서 에러 발생 가능성이 있음 → 이를 방지

두 번째 코드 (아래 코드) → 필터링 없음

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 스터디 하고, 다른 프젝도 마무리 하고, 매일 알고리즘 문제도 풀고 있다보니 시간이 많이 부족한 것 같다.

중간점검때 강사님께서 시간관리에 대한 얘기도 했었는데, 매우 동감하는 부분...

시간 관리나 스케줄 관리를 잘 해야 할 것 같다.. 의욕만 앞서서 살짝 망하는거 같기도함.

그래도 뭐 흥미있고 재미있으니까 만족한다.

댓글을 작성해보세요.


채널톡 아이콘