일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- s3 upload
- Next.js
- react native picker
- Project
- 리액트
- js
- 백준
- AWS
- react native
- 리액트 네이티브
- 리엑트 네이티브 아이콘
- fire base
- img upload
- react native 개발
- AWS Access Key
- 리액트 네이티브 에러
- 문자열 대소문자
- aws bucket 정책
- error
- React
- PongWorld
- 문자열 대소문자 구별
- react native font
- firebase 라이브러리
- react native CLI
- 에러
- babel.config.js
- react native 세팅
- GIT
- Access Key 생성
- Today
- Total
목록분류 전체보기 (105)
밝을희 클태
Blender로 만든 3D 모델을 GLB 파일로 export하여 프로젝트의 메인 페이지에 넣으려고 했다. 그러나 GLB 파일의 크기가 큰 편이라, 네트워크가 조금이라도 느린 상황에서는 빈 화면이 오래 노출될 수 있었다. 이를 방지하기 위해, GLB 파일 다운로드 상태를 실시간으로 표시하는 기능을 구현했다. 진행 상황을 1%부터 100%까지 표현하려고 했다.전체 코드'use client';import React, { useState, useEffect } from 'react';import { Canvas } from '@react-three/fiber';import { Environment, OrbitControls, useGLTF } from '@react-three/drei';function Mode..
미니맵에 도라에몽의 위치를 표시할 때 PNG를 사용해 scale을 줄이니 너무 작아져 화질이 깨지는 문제가 생겨서 직접 그리기로 했다.힘들다..useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext("2d"); img.current.src = "/smallDo.png"; img.current.onload = () => { requestAnimationFrame(init); }; const drawCanvas = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); const PosX = posRef...
이전에도 requestAnimationFrame API를 사용하여 주사율을 동기화한 적이 있지만, 그 당시에는 시간을 측정해 렌더링 스케줄에 이벤트를 등록하지 않는 방식으로 진행했다. 이번에는 이전의 방식과 달리, 모든 이벤트를 렌더링 스케줄에 등록하되, 모든 주사율에서 속도를 일정하게 유지하고 싶었다. 이전 방식에서는 렌더링 스케줄에 이벤트를 등록하지 않다 보니, 높은 주사율을 가진 모니터에서도 60 FPS 애니메이션을 보게 되는 한계가 있었다. 이러한 문제를 해결하기 위해, 이번 구현에서는 각 프레임에서 걸린 시간을 기반으로 속도를 조정함으로써 다양한 주사율에서도 일관된 애니메이션 속도를 제공할 수 있도록 하였다.주사율 동기화 구현drawCanvas 함수 내에서 timestamp를 받아와 각 프레임 ..
총 게임을 하다 보면 벽에 총알 자국이 생겼다가 시간이 지남에 따라 옅어지면서 사라지는 효과를 구현하고 싶었다.먼저 총알 자국 PNG 이미지를 준비한다. 이 이미지는 나중에 게임에서 총알 자국을 그릴 때 사용된다.이전의 총알 자국들을 기억하기 위해 bulletMarks라는 배열을 만들고, 배열 안에 총알 객체를 저장한다. 각 총알 자국 객체는 다음과 같은 속성을 가진다x, y: 총알 자국의 좌표t: 총알 자국이 생긴 시간rotate: 총알 자국을 랜덤한 각도로 회전시키기 위한 값각도는 2π 라디안(360도)으로 표현할 수 있으며, 랜덤한 각도를 만들기 위해 N (0 N N = 0.52π * 0.5 = 1π1π = 180ºN = 0.22π * 0.2 = 0.4π0.4π * 180/π = 72ºN = 0.8..
Canvas로 도라에몽을 움직이는 애니메이션을 구현하던 중, 도라에몽을 잡으면 게임이 종료되고 ‘클리어’ 표시가 나오는 기능을 추가하려고 했다. 그러나 이 과정에서 다음과 같은 문제가 발생했다문제게임 종료 조건이 충족되면 setIsSuccess(true)가 호출되어 isSuccess 상태가 true로 변경되고, Canvas를 담당하는 useEffect의 리렌더링이 발생한다.리렌더링 후 useEffect의 clean-up 함수에서 clearRect()로 Canvas를 지우도록 구현했지만, 이전 애니메이션 프레임들이 남아 있어 피격 효과와 이미지가 전부 지워지지 않아 Canvas가 멈춘 듯 보인다.원인requestAnimationFrame을 사용하여 애니메이션을 구현할 때는 브라우저 렌더링 스케줄에 따라 애..
콜백 지옥?주로 비동기 작업을 순차적으로 실행하기 위해 여러 콜백 함수가 중첩되는 경우 발생한다.비동기 작업에서는 각 작업이 완료된 후에 다음 작업을 수행해야 할 때가 많다. 이때 콜백 함수를 계속 사용하다 보면 코드의 들여쓰기가 깊어지고 중첩되면서 가독성이 떨어지고, 유지보수가 어려워지는 문제가 생긴다. 이를 콜백 지옥이라고 부른다.콜백 지옥 예시아래 코드는 첫 번째 작업이 완료된 후 두 번째 작업이 실행되고, 세 번째 작업이 순차적으로 실행되도록 작성되었다. 하지만 작업이 순서대로 진행되도록 보장하기 위해 콜백 함수가 중첩되면서 코드의 가독성이 떨어지게 된다.setTimeout(() => { console.log("첫 번째 작업 완료"); setTimeout(() => { console.lo..
문제https://school.programmers.co.kr/learn/courses/30/lessons/68646 프로그래머스SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프programmers.co.kr풀이 과정첫 번째 - 시간초과큰 풍선은 무한대로 터트릴 수 있고 작은 풍선은 터트리고 나면 다음번에 연속해서 작은 풍선을 터트릴 수 없다.그러면 살리고 싶은 풍선을 고르고 해당 풍선을 기점으로 왼쪽의 최소값과 오른쪽의 최솟값을 찾아서 살리고 싶은 풍선이 양쪽의 최솟값 풍선보다 크다면 해당 풍선은 터지고 그게 아니라면 생존할 수 있다.그리고 양쪽 끝에 있는 풍선과, 최소값을 가지는 풍선은 무조건 생존할 수 있다. 타겟 풍선을 기준으로 양쪽에 있..
HTML의 캔버스 태그를 공부해 보고 싶었는데 뭘 만들까 생각하다가 어둠 속에서 도라에몽을 찾는 게임을 만들고 싶어졌다.기초는 공식 문서랑 어떤 분이 작성한 개발 블로그를 보고 어느 정도 canvas 태그에 대해 익숙해지고 본격적으로 구현에 들어갔다. 일단 가장 먼저 스트라이트 시트를 골라야 한다스프라이트 시트(sprite sheet)는 여러 개의 작은 그래픽을 그리드(grid)에 정렬하여 구성한 비트맵 이미지 파일이다.게임 개발에서 캐릭터의 연속적인 키 포즈를 한 장의 이미지에 구성하여 2D 애니메이션 제작에 사용된다. 나는 도라에몽 스프라이트 시트를 골랐다. 처음에 아래의 이미지를 무료로 받고 사용하려는데 캐릭터의 움직이는 애니메이션을 구현하고 나니 지진이 났다. 내가 잘 못 그렸나 싶었는데 그냥 스..
https://school.programmers.co.kr/learn/courses/30/lessons/86053 프로그래머스코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.programmers.co.kr최근 알고리즘 문제 중에 가장 오래 걸린 문제였다..풀이 방법신도시의 필요한 자원을 모두 운반할 수 있는 가장 빠른 방법을 찾아야 한다. 단순 구현으로 풀 수는 있지만 시간 초과가 날 수 있어서 이진 탐색으로 방향을 바꿨다. 이진 탐색에서 중요한 건 어떤 기준을 잡고 검사할지와 최대 값을 정하는 부분이다.최악의 경우 구하기가장 빠른 시간을 구해야 하므로 기준은 시간을 잡는다. 최소 시간은 1이고, 최악..
상품 페이지에서 사용자가 상품 이미지를 클릭하면, PhotoSwipe 라이브러리를 통해 큰 사이즈의 이미지가 원본 크기로 표시된다. 네트워크 속도가 빠를 때는 문제가 없지만, 개발자 도구에서 네트워크를 느린 4G로 제한한 경우, PhotoSwipe가 초기화되고 이미지를 표시하기까지 지나치게 오랜 시간이 걸린다. 일반적인 상황에서는 PhotoSwipe이 빠르게 뜨지만, 느린 네트워크에서는 사용자가 이미지를 클릭한 후 아무 반응 없이 기다리는 시간이 길어지면서 사이트가 제대로 작동하지 않는 것처럼 보일 수 있다. PhotoSwipe가 뜨고 나서 이미지를 로딩하는 화면을 보여주면 사용자는 이미지가 로딩 중이라는 것을 인지할 수 있다. 그러나 현재는 이미지 클릭 후 PhotoSwipe 자체가 뜨기까지 긴 시간이..
Next.js 14 App router사이트를 검색했을 때 노출 결과를 사이트의 특성에 맞게 자세하게 노출되게 하고, 사이트의 브라우저 탭에는 간결하게 사이트 이름을 보여주고 싶었다. 방법은 사이트 방문자가 일반 사용자인지 크롤러 봇인지 구분해서 metadata를 주면 된다.import { headers } from 'next/headers';import { userAgent } from 'next/server';export async function generateMetadata({}) { const { isBot } = userAgent({ headers: headers() }); const title = isBot ? 'KEYNUT - 커스텀 키보드등 다양한 전자기기 중고거래는 키넛' : 'KE..
프로젝트의 대부분의 media query를 사용해서 반응형을 구현했다. 하지만 상품 상세 페이지의 경우 모바일일 때는 이미지 슬라이더를 터치 슬라이더로 구현을 하고 싶었고 PC의 경우 버튼을 클릭해서 이미지를 넘기게 하고 싶었다. 그래서 클라이언트의 userAgent를 확인해 모바일인지 아닌지 체크를 했고 결과는 내가 원하는 데로 잘 동작을 했다.export function isMobile() { if (typeof window !== 'undefined') { if (navigator.maxTouchPoints >= 1) { const isMobile = /Macintosh|iPad|Mobi|Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini..
분명히 metadata의 description을 "전자기기 중고거래는 키넛에서"로 설정을 해주었다. 그리고 google search console에 색인 생성을 요청하고 며칠 뒤 확인을 해보니 내가 적성한 적이 없는 description이 나오고 있었다. 원인 예측구글의 메타데이터 처리 방식:구글은 메타 설명이 페이지의 실제 콘텐츠와 충분히 일치하지 않거나, 검색자의 의도와 맞지 않는다고 판단될 경우, 페이지의 내용에서 적절한 텍스트를 추출하여 새로운 description을 생성할 수 있다. 따라서, 메타 설명이 페이지 내용과 잘 일치하고, 충분히 구체적이어야 구글이 이를 그대로 사용할 가능성이 높다. 내가 작성한 metadata의 정보가 충분하지 않다고 판단하면 페이지의 정보를 긁어모아 새로운 desc..
제목처럼 자바스크립트는 싱글 스레드이다. 기본적으로 한 번에 하나의 작업만 처리할 수 있다. 즉 모든 작업이 순차적으로 동기적으로 실행된다. 그런데 우리는 자바스크립트에서 비동기 작업을 하고 있다. 어떻게 가능한 걸까? 자바스크립트는 싱글 스레드가 맞다. 하지만 자바스크립트 런타임 환경인 브라우저, Node.js는 멀티 스레드를 지원한다. 예를 들어서 아래의 코드의 동작 순서를 보기 전에 실행 컨텍스트, 태스크 큐, 이벤트 루프, 콜 스택에 대해서 알아야 한다.콜 스택 (Call Stack)콜 스택은 자바스크립트 엔진이 함수 호출을 관리하는 스택 구조의 데이터 구조입니다.실행 컨텍스트가 생성될 때마다 콜 스택에 푸시(push)되고, 실행이 끝나면 팝(pop)되어 콜 스택에서 제거됩니다.실행 컨텍스트 (E..
프로젝트에서 기본 Confirm Modal을 사용하고 있었는데 일단 못생기기도 했고 모바일에서 대화창 숨기기랑 항목이 데스크톱과 다르게 있는데 이 항목을 한번 클릭하면 더 이상 Confirm Modal이 뜨지 않는다. 우리 프로젝트에서 Confirm Modal의 입력을 꼭 "예" 를 받아다 다음 상황으로 진행이 되기 때문에 실수로 대화창 숨기기를 클릭해 버리면 사용자는 왜 다음 상황으로 안 넘어가는지 알 수 없는 문제가 발생했다. 그래서 useContext API를 사용해 전역에서 사용가능한 Confirm Modal을 만들어 볼거다. ModalProvider.jsx 형태용도modalMessageString모달의 메인 메세지modalSubMessageString모달의 서브 메세지isSelectableMod..
PWA를 도입할 생각은 없었는데 모바일 환경에서 잘 작동하기도 해서 PWA를 프로젝트에 도입하기로 했다.역시나 쉽게 되는 건 없다. 에러가 난다.Please check your GenerateSW plugin configuration: [WebpackGenerateSW] 'images' property is not expected to be here. Did you mean property 'mode'? 구글링을 해도 비슷한 에러는 많이 나왔는데 내가 난 에러는 없는 것 같았다. 계속 next.config를 만져보고 이것저것 건드려도 안 됐다. 그러다가 npm 사이트에서 pwa 패키지의 최근 업데이트 날짜를 보는데 지금 내 Next.js가 14.2.5버전이고 한 달 전쯤 나왔으니까 이게 될 리가 없을 거..
드디어 프로젝트가 끝이 났다. 최근에 커스텀 키보드에 빠져서 커스텀 키보드를 중심으로 중고거래 사이트를 만들고 싶었다. 번개장터, 중고나라 등이 있었지만 키보드에 대한 세세한 카테고리가 없어서 프론트엔드 친구 한 명을 붙잡고 같이 프로젝트를 하자고 했고, 그로부터 두 달 반 정도가 지난 뒤 프로젝트가 마무리 됐다. 사이트의 이름은 keynut으로 정했다. 'keyboard' + 'nut' 의 합성어로 키보드에 미친 사람... 이런 느낌이다. 제대로 배포를 하려고 만든 사이드 프로젝트는 처음이어서 처음에 뭘 어떻게 해야 할지 고민이 정말 많았다. DB는 뭘 써야 하지, 어떤 프레임워크와 라이브러리를 사용할까, 백엔드는 어떻게 구하지 등등 시작이 너무 막막했다. 가장 큰 문제가 프론트엔드 두 명이서 ..
두 명이서 개발을 진행하던 중, S3 GET요청 횟수를 봤는데 2만 회의 GET요청을 보내고 있는 걸 확인했다. 두 명이서 개발을 하고 있고 사이트에 s3 버킷의 이미지가 그렇게 많지 않아 큰 문제가 없을 거라 예상을 했는데 실제 사용자가 생기면 이것보다 훨씬 많은 GET 요청이 생길 거라 예상을 하고 요청을 최소화해야겠다고 생각을 했다.그래서 알아본 게 AWS CloudFront이다.CloudFront란?CDN(Content Delivery Network) 서비스다.CDN은 클라이언트가 요청한 콘텐츠를 제공할 때, 이전에 요청된 콘텐츠라면 엣지 로케이션(Edge Location)에 캐시 된 콘텐츠를 빠르게 제공하는 역할을 한다. 만약 처음 제공되는 콘텐츠라면 해당 콘텐츠를 엣지 로케이션에 캐시로 저장해..
기존에 이미지 슬라이더가 이미지 드래그 도중에 아래 또는 위로 스크롤을 하면 스크롤이 됐다. 그래서 터치 이벤트가 시작될 때 기본 동작을 막아버렸는데 이렇게 되면 문제가 이미지 슬라이더 영역에서는 스크롤이 되지 않아 그 외의 영역을 스크롤해야 했다. 더 좋은 방법이 없을까 고민을 하다가 다른 대기업들의 이미지 슬라이더를 한참을 만져보다가 아이디어가 떠올랐다. 터치 이벤트가 시작이 되고 드래그가 일어날때 y, x의 이동거리를 계산해서 y가 더 크다면 페이지를 스크롤하고, x의 이동거리가 더 크다면 이미지 슬라이더가 드래그되게 하면서 동시에 'touchmove' 이벤트의 기본동작을 막아준다. 정말 별거 아니지만 여기 까지 오는데 시간이 엄청 오래 걸렸다... touchstart event const s..
애플에서 iPad의 userAgent를 Mac이랑 통합을 해버려 아래의 코드만으로 ipad 인지 체크를 할 수 없다./iPad/.test(navigator.userAgent); 실제로 userAgent를 확인해 보면 useAgent에 iPad가 사라져 있다.iPad - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15MacBook - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15 ch..
Binaries: Node: 22.3.0 npm: 10.8.1 Yarn: 1.22.19 pnpm: 8.14.0Relevant Packages: next: 14.2.5 // Latest available version is detected (14.2.5). eslint-config-next: 14.2.3 react: 18.3.1 react-dom: 18.3.1 typescript: 5.4.5 현재 개발 환경에서 테스트를 마치고, 배포 환경에서 프로젝트를 테스트 중인데, 주로 revalidateTag를 사용해서 재검증을 요청하는 방식으로 구현했다. 다른 페이지들은 전부 문제가 없었는데, 메인 페이지(’/’)에서 재검증 요청이 제대로 이루어지지 않는 문제가 발생했다. 검색해보니까 작년부터 나와..
기본 슬라이더 말고 모바일에서 상품을 크게 보기 위해서 큰 사이즈의 이미지 슬라이더를 구현했다. 그런데 핀치 줌으로 이미지를 확대, 축소를 하면 기존에 이미지를 넘기기 위해 구현한 Drag기능이 동작을 한다그래서 터치를 했을 때 지금 몇 개의 손가락이 터치됐는지 확인하는 로직을 추가해 줬더니 내가 원하는 방식으로 잘 작동했다const move = e => { if (isPinching.current) return;};const startTouch = e => { if (e.touches.length > 1) { isPinching.current = true; return; }};imageShow?.addEventListener('touchstart', startTouch);
Next.JS 14 App Router프론트엔드SSR// URN = /shop// 모든 상품 조회const res = await fetch(${process.env.NEXT_PUBLIC_BASE_URL}, { next: { tags: ['products'] },});// URN = /// 최근 등록된 상품 일부 조회const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/products/recent`, { next: { tags: ['products'] },});// URN = /product/[id]// 등록된 상품 조회const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}..
Next.JS 14 App Router 그래서 Next.JS에서는 기본적으로 서버 사이드에서 GET 요청을 하면 서버 메모리에 데이터 캐시를 확인해 일치하는 캐시가 있으면 캐시 된 데이터를 제공한다. 이 캐시 데이터의 재검증하는 로직을 따로 두지 않으면 서버가 재시작되기 전까지는 항상 같은 캐시 데이터를 제공한다.DurationThe Data Cache is persistent across incoming requests and deployments unless you revalidate or opt-out.By default, data requests that use fetch are cached. You can use the cache and next.revalidate options of fetc..
Next.JS 14 App Router 회원 관리나 상품 관리를 편하게 하기 위해 어드민 페이지를 만들려고 한다.어드민 페이지의 기능은 아래와 같이 4가지 기능이 있다.전체 유저 조회정지된 유저 조회전체 상품 조회신고가 들어온 상품 조회로직 :URL의 queryString을 읽어서 page, 검색어를 받아옴queryString의 값으로 DB를 조회page나 검색어가 변경이 되면 URL에 반영hook으로 안 하고 queryString으로 하는 이유는 hook으로 구현을 하면 새로고침시 현재 보던 페이지, 검색 키워드, 카테고리 등이 다 초기화가 된다. 그리고 내가 지금 보는 화면을 다른 사람과 공유를 할 수 없어서 queryString을 읽어서 구현을 했다. 어드민 페이지를 만들다가 기존의 레이아웃들 때..
failed to load resource the server responded with a status of 431 ()프로젝트 진행 도중 갑자기 한 유저로 로그인을 하면 위와 같은 에러가 나고 아예 사이트로 접근이 불가능해졌다.이 에러가 발생하기 바로 직전 더미 데이터로 테스트를 하려고 MongoDB에 상품 1000개를 넣었다. 그러고 나서 에러가 발생!!원인 - NextAuth를 사용 중인데 로그인을 할 때 users collection에서 해당 user의 정보를 다 가져와 jwt token에 저장을 한다. 방금 1000개의 더미 데이터를 넣으면서 해당 user의 document에 상품의 ID를 넣는다. 그러면 로그인을 할 때 user document의 data를 다 가져오려하고 데이터가 너무 커서..
searchParams에 대해서 정리를 해볼려고 한다. searchParams는 URL의 query string을 칭한다. searchParams를 읽어서 특정 데이터를 가져오거나, 카테고리 등 많은 곳에 사용이 된다. https://keynut.co.kr?keyword=hello&page=3위 URL에서 노란색 부분이 searchParams가 된다. searchParams를 읽는 방법은Next.js 14 App Router에서는 useSearchParams 훅을 사용하여 읽기 전용의 searchParams 객체를 리턴받을 수 있다.useSearchParams 훅으로 리턴받은 searchParams는 읽기 전용이므로 직접 수정할 수 없다. 대신, URLSearchParams 객체를 사용해 searchPa..
프로젝트를 진행하다가 상품 업로드를 하고 상품을 조회할 때 옛날의 데이터가 미리 렌더링 되고 그 이후에 최신 데이터로 바뀌는 게 너무 별로라서 왜 그런지 궁금했다. 일단 상품을 렌더링 하는 로직이const getProducts = async queryString => {... const res = await fetch(url);...};export default getProducts;서버 사이드에서 react query를 사용해서 data prefetching(getProduct)클라이언트 사이드에서 useQuery를 사용해 서버에서 미리 받아온 데이터를 렌더링react query 내부적으로 백그라운드에서 데이터 재검증이 일어나 최신 데이터를 다시 렌더링 원인은 위에 로직을 봐서 알겠지만 서버 사..
프로젝트를 진행하던 도중 테스트를 하고 있는데 아이폰에서 찍은 사진을 맥북으로 받아와 업로드하려고 하는데서 문제가 생겼다. 기존에 아래처럼 accept props에 이미지의 모든 확장자를 다 받게 설정을 해뒀는데 HEIC 이미지는 여기에 해당이 되지 않아서 이미지들이 선택이 되지 않았다. 아래처럼 image/heic를 추가해 주면 HEIC 이미지들도 input에서 선택이 가능하다. 그리고 이제 HEIC 파일들을 jpeg로 변환해 주기 위해 heic2any 라이브러리를 다운로드해야 한다. 해당 라이브러리는 HEIC 파일을 JPEG 또는 PNG로 변환할 수 있다.$ npm install heic2anyhttps://www.npmjs.com/package/heic2any heic2anyConverting ..
환경Next.js 14 APP RouterAWS SDK for Javascript v3 https://gaebarsaebal.tistory.com/79 [ AWS ] S3 버킷(bucket) 생성, 사용자 추가, 액세스 키 만들기전에 작성한 글이 있는데 너무 대충 설명한 거 같아서 다시 써야겠다.버킷 만들기1. aws에서 s3를 검색해서 들어와 버킷 만들기로 들어간다.2. 버킷 이름을 적는다.3. 나는 현재 계정에서만 사용할gaebarsaebal.tistory.comS3 bucket 설정은 이 전 포스팅에서 다룬다. 나는 S3 이미지 업로드를 서버에서 할 것이다. 클라이언트에서 할 수도 있지만 클라이언트에서 하면 AWS key가 노출될 위험이 있고 Presigned URL을 사용해 key의 노출을 막을..