프로그래밍 공부방

[Next.js] vercel에서 aws s3 사용 방법 본문

프론트엔드/Next.js

[Next.js] vercel에서 aws s3 사용 방법

김갱갱 2023. 6. 19. 21:49

🐥vercel에서 aws s3 사용 방법

오늘은 vercel에서 aws s3 사용하는 방법에 대해서 알아보겠습니다.

기존에 s3을 사용하기 위한 코드는 vercel 배포 후에 작동이 되지 않는 문제가 생겼었습니다 ㅠㅠ

따라서 vercel에서 사용할 수 있도록 코드를 새롭게 짰습니다.


1. upload-url.ts

일단 upload-url 파일을 먼저 보겠습니다.

이 파일에서는 s3에 이미지를 업로드할 수 있는 url을 생성할 수 있습니다.

// upload-url.ts

import S3 from "aws-sdk/clients/s3";
import { NextApiRequest, NextApiResponse } from "next";
import getExtension from "@libs/client/getExtension"; // 파일 확장자 추출

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const s3 = new S3({ // aws s3 정보 입력
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    region: process.env.AWS_REGION,
  });

  const { file, fileType, userId, type } = req.query;
  const { fileExtenstion } = getExtension(file as string); // 이미지 파일 확장자
  const nFilename = (type === "profile" ? "profile/" : "product/") + Date.now() + "_" + userId + fileExtenstion;

  const image = await s3.createPresignedPost({
    Bucket: process.env.BUCKET_NAME,
    Fields: {
      key: nFilename,
      "Content-Type": fileType,
    },
    Expires: 60, // seconds
  });

  res.json({
    ok: true,
    image,
    nFilename,
  });
}

본인의 AWS S3 키 , 지역 정보 등을 환경변수 처리해서 넣어준 후 new S3를 만들어줍니다.

그리고 createPresignedPost를 이용해서 이미지를 저장할 수 있는 url을 생성할 수 있습니다.

createPresignedPost에 key값이 있는 것을 볼 수 있는데 이 key값이 파일의 이름이 됩니다.

image를 console로 찍어보면 아래와 같이 url이 있는 것을 확인하실 수 있습니다.

 

image를 console로 찍어봤을 때의 결과값

2. upload.tsx

실제로 s3에 이미지를 업로드하는 작업을 한 파일입니다.

// upload.tsx

import { S3 } from "aws-sdk";
import useUser from "@libs/client/useUser";
import useSWR from "swr";

interface UploadProduct {
  ok: boolean;
  image: S3.PresignedPost;
  nFilename: string;
}

interface UploadProductMutation {
  ok: boolean;
  product: Product;
}

const Upload: NextPage = () => {
  const router = useRouter();
  const { user } = useUser();
  const { register, handleSubmit, watch } = useForm<UploadProductForm>();
  const productImage = watch("image");
  const { data: uploadUrl } = useSWR<UploadProduct>(
    productImage && productImage[0] && user
      ? `/api/upload-url?file=${productImage[0].name}&fileType=${productImage[0].type}&userId=${user?.id}&type=product`
      : null
  );
  const [uploadProduct, { loading, data }] = useMutation<UploadProductMutation>(
    "/api/products",
    "POST"
  );

  const onValid = async ({ name, price, description }: UploadProductForm) => {
    if (loading || !uploadUrl) return;
    const {
      nFilename,
      image: { url, fields },
    } = uploadUrl;
    const file = productImage[0];
    const fd = new FormData();
    Object.entries({ ...fields, file }).forEach(([key, value]) => {
      fd.append(key, value as string);
    });
    const uploadImage = await fetch(url, { method: "POST", body: fd });
    uploadProduct({
      file: uploadImage && uploadImage.ok ? nFilename : null,
      name,
      price,
      description,
    });
  };

 

코드가 길어서 나눠서 알아보겠습니다.

 

const { data: uploadUrl } = useSWR<UploadProduct>(
    productImage && productImage[0] && user
    ? `/api/upload-url?file=${productImage[0].name}&fileType=${productImage[0].type}&userId=${user?.id}&type=product`
    : null
);

위에서 만든 upload-url의 결과값을 받아오는 부분입니다.

저는 이미지를 저장할 때 기존 파일 이름을 기준으로 제가 새롭게 만든 이름으로 저장하고 싶었기 때문에 query를 저렇게 설정해주었습니다.

const onValid = async ({ name, price, description }: UploadProductForm) => {
    if (loading || !uploadUrl) return;
    const {
      nFilename,
      image: { url, fields },
    } = uploadUrl;
    const file = productImage[0];
    const fd = new FormData();
    Object.entries({ ...fields, file }).forEach(([key, value]) => {
      fd.append(key, value as string);
    });
    const uploadImage = await fetch(url, { method: "POST", body: fd });
    uploadProduct({
      file: uploadImage && uploadImage.ok ? nFilename : null,
      name,
      price,
      description,
    });
};

우선 upload-url에서의 결과값을 가져와줍니다.

file은 제가 <input type="file">로 업로드해서 저장할 이미지 파일입니다.

새로운 formdata를 생성해준 후 upload-url에서 가져온 fields 정보와 file정보를 모두 추가해줍니다.

그리고 upload-url을 통해 생성한 url을 이용해서 이미지를 업로드 해줍니다.

이 작업까지 마쳤을 경우 aws s3 대시보드로 가보면 이미지가 업로드된 것을 확인할 수 있습니다.

 

이제 그 아래 uploadProduct({})는 제 db에 업로드된 이미지의 url을 넣어주기 위해 Post작업을 하는 함수입니다!

3. index.tsx

db에 s3 이미지 주소를 넣어주는 작업을 한 파일입니다.

// index.ts --> api 폴더

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  ...
  if (req.method === "POST") {
    const {
      session: { user },
      body: { file, name, price, description },
    } = req;
    const location = `https://${process.env.BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${file}`;
    const product = await client.product.create({
	...
  }
}
export default withApiSession(
  withHandler({
    methods: ["GET", "POST"],
    handler,
  })
);

s3 이미지 주소는 location에 해당합니다. 

s3에 해당하는 이미지 주소를 만들어서 db에 넣어주는 작업을 했습니다.


✨결과물

 


🐥💬

👉출처: https://vercel.com/guides/how-can-i-use-aws-s3-with-vercel👈

 

How to Use AWS S3 Buckets with Vercel

Learn how to use Amazon Web Services (AWS) S3 buckets together with Vercel to upload, store, and retrieve objects.

vercel.com