[etc] PT.4 - React 프로젝트 이미지 성능 최적화 시키기 (React 프로젝트에 적용하기.)

김채운·2023년 10월 31일
0

etc.

목록 보기
5/9
post-thumbnail
post-custom-banner

S3 버킷에 이미지 업로드 하기.

  1. npm i @aws-sdk/client-s3 설치.
import { S3Client, S3ClientConfig, PutObjectCommand } from "@aws-sdk/client-s3";

function Upload() {
    const awsRegion = process.env.REACT_APP_REGION;
    const awsAccessKeyId = process.env.REACT_APP_AWS_ACCESS_KEY_ID as string;
    const awsSecretAccessKey = process.env.REACT_APP_AWS_SECRET_ACCESS_KEY as string;
    const s3Bucket = process.env.REACT_APP_BUCKET_NAME;

    const s3ClientConfig: S3ClientConfig = {
        region: awsRegion,
        credentials: {
            accessKeyId: awsAccessKeyId,
            secretAccessKey: awsSecretAccessKey,
        },
    };

    const s3Client = new S3Client(s3ClientConfig);

    const handleUpload = async () => {

        try {
            const file = fileInput?.current?.files?.[0];
            const fileName = file?.name
            const formData = new FormData();
            const params = {
                Bucket: s3Bucket,
                Key: `dev/${fileName}`, // 'dev' 폴더에 업로드
                Body: file,
                ACL: 'public-read', // 이미지를 공개로 설정
            };

            await s3Client.send(new PutObjectCommand(params));

            formData.append("product_name", productName || "")
            formData.append("image", file || "");
            formData.append("price", String(productPrice))
            formData.append("shipping_method", shippingCheck ? "PARCEL" : "DELIVERY")
            formData.append("shipping_fee", String(shippingFee))
            formData.append("stock", String(productStock))
            formData.append("product_info", `${productName} 입니다.`)
            formData.append("token", token || "")
            console.log(formData)
            dispatch(uploadProduct(formData))
        } catch (error) {
            console.log('Error uploading image to S3 or uploading product information:', error);
            throw error;
        }
    }

    return (
      <Button width="200px" font_size="18px" height="60px" margin="0 0 0 14px" _onClick={handleUpload}>저장하기</Button>
                    )
}

s3ClientConfig: 이 객체는 AWS S3 클라이언트를 구성하는 데 사용된다. S3 클라이언트 설정을 정의하고, AWS 리전과 인증 자격 증명 정보를 설정한다.

region: S3 버킷이 위치한 AWS 리전을 설정합니다.

credentials: AWS accessKeyId 및 secretAccessKey를 설정한다. 이것들은 S3에 액세스하기 위한 인증 정보이다.

s3Client: s3Client는 AWS SDK에서 제공하는 S3 클라이언트를 생성한다. 이 객체는 실제 S3 작업을 수행하고 S3 버킷과 상호 작용하는 데 사용된다.

handleUpload: 이 함수는 이미지 업로드 및 다른 제품 정보를 S3 버킷에 저장하는 로직을 처리한다.

params: handleUpload 안에 있는 상수 params 객체는 이미지 업로드에 필요한 매개변수를 설정한다. 이 객체는 Bucket,Key,Body,ACL과 같은 요소로 구성되어있다.

Bucket: 이미지를 업로드할 S3 버킷 이름을 설정한다.

Key: 이미지가 S3 버킷에 저장될 경로와 파일 이름을 설정합니다. 위의 코드에서는 'dev' 폴더에 이미지를 저장하며 파일 이름은 fileName 변수에 따라 결정된다.

Body: 업로드할 이미지 파일의 데이터를 설정한다.

ACL: 이미지가 공개 읽기 권한을 가지도록 'public-read'로 설정한다. 이렇게 하면 누구나 이미지에 액세스할 수 있다.

s3Client.send: send메서드를 사용해서 'PutObjectCommand'를 호출하여 이미지를 S3에 업로드한다. 이 명령은 params 객체에 설정된 내용에 따라 이미지를 업로드합니다.

PutObjectCommand: PutObjectCommand는 AWS SDK에서 제공하는 S3 명령 중 하나로, 주어진 파일(또는 데이터)을 AWS S3 버킷에 업로드하기 위한 명령이다. 이 명령은 S3 버킷에 객체(파일 또는 데이터)를 추가하는 데 사용된다. 설정된 매개변수를 사용하여 PutObjectCommand를 생성하고, 해당 명령을 S3 서비스로 보내서 객체를 업로드한다. 이 명령을 실행하면 S3 버킷에 객체(파일 또는 데이터)가 업로드된다.

리사이징 된 이미지 가져오기.

   <Container>
            {
                list.map((p, i) => {
                    return <div key={p.product_id}>
                        <img src={
                            `https://d2a0m4zl4hi5gz.cloudfront.net/dev/${(p.image).substring((p.image).lastIndexOf("/") + 1)}?w=380&h=380`
                        }
                            onError={(e: React.ChangeEvent<HTMLImageElement>) => {
                                e.target.onerror = null; // 에러 핸들러 무한 루프 방지
                                e.target.src = p.image; // 이미지 로드 실패 시 p.image 사용
                            }}
                            alt=""
                            onClick={() => navigate(`/detail/${p.product_id}`)}
                        />
                        <p className='product-name'>{p.store_name}</p>
                        <p className='product'>{p.product_name}</p>
                        <span className='product-price'>{p.price.toLocaleString()}</span>
                        <span></span>
                    </div>
                })
            }
            {moreData ? <div ref={target}></div> : null}
        </Container>

img태그의 src에 cloudfront 도메인을 사용하고 s3에 생성해둔 dev/ 폴더를 통해서 s3버킷에 저장되어 있는 이미지를 가져온다 그리고 ?뒤로 리사이징할 이미지 사이즈를 쿼리로 붙여주면 s3에 업로드되어 있던 이미지를 리사이징 해서 가져올 수 있게 된다.

(p.image).substring((p.image).lastIndexOf("/") + 1)

이 부분은 이미지 url에서 이미지 파일 이름만 추출하기 위한 식인데 설명하자면,

p.image가 'https://openmarket.weniv.co.kr/media/products/2023/10/10/tumbler-mint.jpg'라면,

  1. lastIndexOf메서드를 사용해서 문자열 p.image에서 가장 마지막 슬래시('/')문자의 인덱스를 찾는다. (* lastIndexOf 메서드는 문자열 내에서 지정된 문자 또는 부분 문자열의 가장 마지막 발생 위치를 반환한다.)

  2. '+1'을 추가해서 마지막 슬래시 다음 문자의 인덱스를 얻는다. 이로서 파일 이름의 시작 위치가 결정된다.

  3. 마지막으로 'substring'메서드를 사용해서 원본 URL문자열에서 파일 이름만 추출한다.(*substring메서드는 javascript 문자열 객체의 메서드 중 하나로, 문자열에서 부분 문자열을 추출하는 데 사용된다. 이 메서드는 문자열 내의 시작 인데스와 종료 인덱스를 지정하여 부분 문자열을 반환한다.)

onError

'onError' 부분은 이미지 로딩 중에 오류가 발생했을 때 처리하기 위한 부분이다. 이 부분은 'img' 요소의 'onError'이벤트 핸들러로 설정되어 있다. 그렇기 때문에 이미지 로딩 중에 오류가 발생하면 이 핸들러가 실행된다.

e.target.onerror=null: 이 부분은 무한루프를 방지하기 위한 코드이다. 이미지 로딩 중에 오류가 발생하면 'onError'핸들러가 계속 호출되는 것을 방지한다. 그래서 이미지 로딩 중 요루가 발생했을 때 'onError'핸들러를 다시 실행하지 않도록 설정한다. (*e.target은 이벤트가 발생한 요소, 즉 'img'요소를 나타낸다.)

e.target.src = p.image: 현재 내 프로젝트는 이미지 업로드를 나만 하는 게 아니라 다른 사람들도 판매할 물건의 이미지를 올릴 수 있는 프로젝트이다. 그래서 나는 이미지를 s3버킷에 업로드 하지만 다른 사람들은 오픈마켓 서버에 이미지를 업로드 하는 사람들도 있다. 그렇기 때문에 s3버킷에 이미지가 있는 경우에는 배포한 cloudfront 도메인을 사용해서 이미지를 가져오지만, s3버킷에 이미지가 없는 경우에는 오픈마켓 서버에 있는 이미지를 가져와야 하기 때문에 p.image를 사용한다. 그래서,
s3버킷에 이미지가 없어서 error가 발생하면 'onError' 핸들러가 실행되고 e.target.src=p.image로 img의 src 요소를 p.image로 한다고 설정해둔 거다.

최적화 결과

최적화 하기 전

  • 최적화 하기 전에 오픈마켓 서버에서 이미지를 가져와서 css로 380px로 이미지를 만들어줬을 때

최적화 하고난 후

  • 최적화 하고 난 후 s3버킷에 업로드 되어있는 이미지를 lambda@edge함수를 통해 리사이징 하고 cloudfront서버에서 가져왔을 때

용량은 4.4KB에서 313B로 줄어들었고 시간도 94ms에서 17ms로 줄어들었다. 시간뿐만 아니라 용량도 줄어든 걸 확인할 수 있다.

post-custom-banner

0개의 댓글