![[인프런 워밍업 클럽 3기] 풀스택 과정 2주차 발자국 👣](https://cdn.inflearn.com/public/files/blogs/f41f921e-6045-4100-8db0-cdb24fd25ef9/스크린샷 2025-03-16 오후 3.00.12.png)
[인프런 워밍업 클럽 3기] 풀스택 과정 2주차 발자국 👣
2주차에 배운 내용을 정리해본다.
1. 배운 내용
1. Next.js에서 메타데이터 정의하기
<meta>
태그를 이용해 사이트 정보를 정의하려면,
서버 컴포넌트 파일에서
Metadata를 정의해줘야 함
// page.tsx
import { Metadata } from "next";
import Ui from "./Ui";
// 페이지의 메타데이터를 정의
// use client에서는 사용 불가 - 클라이언트 코드는 Ui.tsx에서 정의하는 이유
export const metadata: Metadata = {
title: "Dropbox Clone",
description: "A minimalist Dropbox Clone",
};
export default function Home() {
return <Ui />;
}
2. 파일 드랍 존 만들기
<input />
태그를 이용,type="file"
3. supabase
1. storage bucket 만들기
업로드 가능한 파일 종류 설정 가능
만들 때 Allowed MIME types 옵션에서 image/* 등의 조건을 추가하면 됨
2. policy 생성
사이드바 policy 메뉴에서 생성 가능
이름, 가능한 액션 종류 선택, 누구에게 가능하게 할지 선택 가능
4. 파일 드래그앤드롭 - react-dropzone 라이브러리 사용
사용법은 npm 공식 문서의 코드 조각을 확인
https://www.npmjs.com/package/react-dropzone
2. 이슈 사항
1. storage의 get url 형태 변경: getImageUrl 함수 커스텀 어려움
이미지의 만료일을 지정할 수 있게 변경됨
그러면서 token이라는 서치 파라미터가 필수값으로 추가된 듯
토큰을 누락한 형태로 확인 시 에러가 발생하며 이미지 로드 실패
{"statusCode":"400","error":"Error","message":"querystring must have required property 'token'"}
bucket을 public으로 전환하고, supabase에서 제공하는
getPublicUrl
메서드를 사용storage에서 bucket 이름 옆 드롭다운 메뉴 → edit bucket → public으로 설정
getImageUrl
함수 내부를 아래와 같이 수정const { data } = supabase.storage .from(process.env.NEXT_PUBLIC_STORAGE_BUCKET!) .getPublicUrl(path);
/** * A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset. * This function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset. * * @param path The path and name of the file to generate the public URL for. For example `folder/image.png`. * @param options.download Triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. */ getPublicUrl( path: string, options?: { download?: string | boolean; transform?: TransformOptions } ): { data: { publicUrl: string } } { const _path = this._getFinalPath(path) const _queryString = [] const downloadQueryParam = options?.download ? `download=${options.download === true ? '' : options.download}` : '' if (downloadQueryParam !== '') { _queryString.push(downloadQueryParam) } const wantsTransformation = typeof options?.transform !== 'undefined' const renderPath = wantsTransformation ? 'render/image' : 'object' const transformationQuery = this.transformOptsToQueryString(options?.transform || {}) if (transformationQuery !== '') { _queryString.push(transformationQuery) } let queryString = _queryString.join('&') if (queryString !== '') { queryString = `?${queryString}` } return { data: { publicUrl: encodeURI(`${this.url}/${renderPath}/public/${_path}${queryString}`) }, } }
StorageFileApi.ts
를 참고하면 다양한 메서드가 있음 -createSingedUrl
를 이용하면expiresIn
을 직접 지정 가능. 이걸 이용하면 bucket이 public이 아니어도 가능할듯/** * Creates a signed URL. Use a signed URL to share a file for a fixed amount of time. * * @param path The file path, including the current file name. For example `folder/image.png`. * @param expiresIn The number of seconds until the signed URL expires. For example, `60` for a URL which is valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. * @param options.transform Transform the asset before serving it to the client. */ async createSignedUrl( path: string, expiresIn: number, options?: { download?: string | boolean; transform?: TransformOptions } ): Promise< | { data: { signedUrl: string } error: null } | { data: null error: StorageError } > { try { let _path = this._getFinalPath(path) let data = await post( this.fetch, `${this.url}/object/sign/${_path}`, { expiresIn, ...(options?.transform ? { transform: options.transform } : {}) }, { headers: this.headers } ) const downloadQueryParam = options?.download ? `&download=${options.download === true ? '' : options.download}` : '' const signedUrl = encodeURI(`${this.url}${data.signedURL}${downloadQueryParam}`) data = { signedUrl } return { data, error: null } } catch (error) { if (isStorageError(error)) { return { data: null, error } } throw error } } /** * Creates multiple signed URLs. Use a signed URL to share a file for a fixed amount of time. * * @param paths The file paths to be downloaded, including the current file names. For example `['folder/image.png', 'folder2/image2.png']`. * @param expiresIn The number of seconds until the signed URLs expire. For example, `60` for URLs which are valid for one minute. * @param options.download triggers the file as a download if set to true. Set this parameter as the name of the file if you want to trigger the download with a different filename. */ async createSignedUrls( paths: string[], expiresIn: number, options?: { download: string | boolean } ): Promise< | { data: { error: string | null; path: string | null; signedUrl: string }[] error: null } | { data: null error: StorageError } > { try { const data = await post( this.fetch, `${this.url}/object/sign/${this.bucketId}`, { expiresIn, paths }, { headers: this.headers } ) const downloadQueryParam = options?.download ? `&download=${options.download === true ? '' : options.download}` : '' return { data: data.map((datum: { signedURL: string }) => ({ ...datum, signedUrl: datum.signedURL ? encodeURI(`${this.url}${datum.signedURL}${downloadQueryParam}`) : null, })), error: null, } } catch (error) { if (isStorageError(error)) { return { data: null, error } } throw error } }
2. server action 파일에서 console.log
는 개발자 도구가 아닌 터미널에 찍힌다는 사실..
storageActions.ts
에 정의한uploadFile
함수를 수정하고 제대로 데이터가 들어가는지 확인하려고console.log
를 사용아무리 해도
useQuery
까지는 잘 로그가 찍히는데,uploadFile
에 작성한 로그가 브라우저 개발자도구에 전혀 찍히지 않아 뭐가 문제인지 한참 헤맴코드 에디터 터미널에 찍히고 있었음.. ㅠㅠ
3. 파일명에 한글이 포함될 경우 업로드 안되는 오류
디스코드에 다른 러너분들이 공유해준 정보에 따르면 supabase storage는 AWS의 S3 스토리지와 호횐되어, 파일명도 AWS S3의 Safe charaters - 영문, 숫자, 일부 특수기호 만 허용한다고 함
어떻게 저장할까?
base64 인코딩을 통해 S3-safe한 이름으로 변경해 저장하면 업로드 가능
⇒ 저장 및 다운로드, 이름 표시하는 코드에서 인코딩/디코딩 함수를 사용하게 변경 완료
어떻게 검색할까?
인코딩/디코딩된 값으로 검색 호환이 안됨
⇒ db에 저장해야 함
db에 저장하도록 변경했는데, 한글 초성만 검색됨..
todo-list 검색 때와의 차이가 뭐길래 안되는거지?
3. 미션 - 파일의 마지막 수정(업로드) 시간 표시하기
storage를 확인하면, Added on, Last modified 정보가 있음
file
이 어떤 형태인지 로그 찍어 확인file.updated_at
키에 저장된 값임을 확인, 이 값을 사용추가로
file.metadata
에 파일 타입과 사이즈 등의 정보도 있어 그 정보들도 적절히 표시하도록 함 (ui는 supabase storage를 참고함)
댓글을 작성해보세요.