CLOUDFLARE로 이미지 업로드
2024. 9. 13. 09:37

cloudflare 서비스를 사용해 웹개발 시 사용할 이미지를 업로드하고 사용하는 방법을 포스팅

 

사전준비

우선 cloudflare에 회원가입을하고 대시보드로 들어간 후 메뉴에서 Image를 누른다.

현시점 기준으로 무료 플랜을 선택하고 한 달에 5$를 지불하는 형식으로 선택하면 테스트용으로 충분한 사용량을 제공해 준다.

 

이미지 업로드 플로우 (Next Js, App router 기준)

  1. input으로 이미지 받아서 파일형식, 크기 등 체크
  2. (server action) cloudflare로 이미지 업로드 url, image id 요청
  3. 해당 Form 제출 시 업로드 url로 이미지 업로드
  4. 업로드 성공 후 Delivery URL + image id를 DB에 저장

 

1~2 해당 코드

"use client"

// file 업로드용 Input onChange
const onImageChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const {
      target: { files },
    } = event;
    if (!files) {
      return;
    }
    const file = files[0];

    // Check if the file is an image
    const validImageTypes = [
      "image/jpeg",
      "image/png",
      "image/gif",
      "image/bmp",
    ];
    if (!validImageTypes.includes(file.type)) {
      alert("이미지 파일이 아닙니다.");
      return;
    }

    // Check if the file size is less than 3MB
    const maxSizeInMB = 3;
    if (file.size > maxSizeInMB * 1024 * 1024) {
      alert("3MB 이하의 이미지를 사용해주세요.");
      return;
    }
    
    // 업로드 Url, image id 요청
    const { success, result } = await getUploadUrl();
    
    if (success) {
      const { id, uploadURL } = result;
      setUploadUrl(uploadURL);
      // form의 photo 값에 delivery url set
      setValue(
        "photo",
        `https://imagedelivery.net/S5EmZfh9mNC3-3xmENYiiA/${id}`
      );
    } else {
      alert("이미지 업로드에 실패했습니다");
      return;
    }

    // 브라우저에 올라간 이미지 메모리 주소URL
    const url = URL.createObjectURL(file);
    setPreview(url);
    setFile(file);
}

 

"use server"

// 서버에서 업로드 Url, 이미지 id 받아오기
export async function getUploadUrl() {
  const response = await fetch(
    `
https://api.cloudflare.com/client/v4
/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/images/v2/direct_upload`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.CLOUDFLARE_TOKEN}`,
      },
    }
  );
  const data = await response.json();
  return data;
}

 

3. 이미지 업로드

"use client"

const onSubmit = handleSubmit(async (data: ProductType) => {
    ...
    
    const cloudflareForm = new FormData();
    cloudflareForm.append("file", file); // 이미지 파일 append
    
    // 이미지 업로드
    const response = await fetch(uploadUrl, {
      method: "POST",
      body: cloudflareForm,
    });
    
    if (response.status !== 200) {
      alert("게시물 생성에 실패했습니다.");
      return;
    }

    ...
  });

 

 

업로드된 이미지 URL DB 저장

"use server"

// Form Data DB 업데이트
export async function uploadProduct(formData: FormData) {
  const data = {
    photo: formData.get("photo"),
    title: formData.get("title"),
    ...
  };

  // zod로 validation
  const result = productSchema.safeParse(data);
  
  if (!result.success) {
    return result.error.flatten();
  } else {
    const session = await getSession(); // 로그인 여부
    
    if (session.id) {
      // 새로운 product를 db에 생성
      const product = await db.product.create({
        data: {
          title: result.data.title,
          photo: result.data.photo,
          ...,
        },
        select: {
          id: true,
        },
      });
      redirect(`/products/${product.id}`);
    }
  }
}

 

 

이미지를 직접 업로드하는 게 아니라 업로드 URL을 받아서 처리하는 이유

Cloudflare에서 이미지 업로드 업로드 URL 따로 요청하는 방식은 보통 보안 효율성 위한 것이다.

 

  1. 보안 강화
    • 서버의 직접 노출 방지: 클라이언트가 Cloudflare 서버에 직접 업로드하지 않고, 미리 발급된 서명된 URL 사용하여 이미지를 업로드한다. 방식은 클라이언트가 서버의 실제 업로드 경로를 없게 하여, 서버를 직접 공격하는 가능성을 줄인다.
    • 업로드 제한: URL 제한된 시간 동안만 유효하거나, 특정 조건 하에서만 사용할 있다. 이를 통해 업로드 권한이 불필요하게 확산되는 것을 방지하고, 악의적인 업로드를 막을 있다.
  2. 사전 검증 및 요청 제어
    • 업로드 전에 서버에서 요청을 검증할 있는 기회를 제공한다. 예를 들어, 사용자가 업로드 권한이 있는지, 이미지 크기가 적절한지 등을 확인하고, 검증이 완료된 후에야 업로드 URL 발급하게 된다.
  3. 업로드 트래픽 분산
    • 직접 업로드가 아닌 Cloudflare를 통해 이미지가 업로드되므로, 트래픽이 분산되어 서버의 부하를 줄일 수 있다. 클라이언트가 여러 파일을 업로드할 때, 서버는 URL을 발급하는 것만 담당하고, 실제 업로드는 Cloudflare의 전용 네트워크에서 처리된다.
  4. 유연성
    • 방식은 REST API 같은 방식으로 작동하므로 다양한 클라이언트(, 모바일 )에서 일관된 방식으로 이미지를 업로드할 있다. 클라이언트가 이미지를 직접 Cloudflare 전송하는 대신, 발급된 URL 사용하여 업로드를 처리하게 된.

 

이미지 Variants 기능

 

Cloudflare 이미지 서비스의 Variants 기능은 이미지 최적화를 위한 중요한 도구이다. 이 기능을 통해 원본 이미지를 다양한 크기, 형식, 품질로 변환하여 사용자에게 제공할 수 있다. 주로 웹 성능을 개선하고, 네트워크 대역폭을 절약하며, 사용자 경험을 최적화하는 데 사용된다.

 

  1. 다양한 이미지 크기 제공
    • 원본 이미지를 업로드한 , Variants 사용하여 다양한 크기의 이미지를 정의할 있다. 이를 통해 동일한 이미지를 여러 크기로 제공하여, 모바일, 태블릿, 데스크탑 등의 각기 다른 디바이스에 맞는 적절한 크기의 이미지를 사용할 있다.
  2. 이미지 형식 변환
    • Variants 이용하면 원본 이미지를 자동으로 다른 포맷(: WebP, JPEG)으로 변환하여 제공할 있다. WebP 같은 형식은 JPEG PNG보다 작은 용량을 제공할 있어 성능을 크게 향상시킨다.
  3. 이미지 품질 제어
    • Variants 이미지의 품질도 조정할 있다. 예를 들어, 고화질 이미지를 필요로 하지 않는 경우 품질을 낮춰 네트워크 비용과 로딩 시간을 줄일 있다.
  4. 응답형 이미지
    • 다양한 디바이스에 맞춘 이미지 사이즈를 제공하므로, 화면 크기에 맞는 최적화된 이미지를 제공하여 사용자 경험을 향상시킬 있다. 이를 통해 페이지 로딩 시간을 줄이고, 모바일 데이터 사용량도 절약할 있다.
  5. 손쉬운 URL 기반 액세스
    •  Variant는 고유한 URL을 가지며, 이 URL을 통해 변환된 이미지를 쉽게 요청할 수 있다. 이를 통해 개발자는 복잡한 서버 측 이미지 변환 작업 없이 다양한 이미지 변형을 제공할 수 있다. (Delivery URL + Image Id + Variant)
https://imagedelivery.net/S8EmZfz3mNC1-4xmENYiiA/<image_id>/<variant_name>