환경파일 설정하기

AWS_S3_BUCKET=
AWS_S3_REGION=
AWS_S3_ACCESS_KEY_ID=
AWS_S3_SECRET_ACCESS_KEY=
NEXT_PUBLIC_AWS_CLOUDFRONT_URL=

S3 bucket and region

server action

'use server';
import { PutObjectCommand, PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3';

const s3Client = new S3Client({
  region: process.env.AWS_REGION as string,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
  },
});

export async function uploadFileS3(file: File, fileName: string) {
  const fileBuffer = (await file.arrayBuffer()) as Buffer;
  const params: PutObjectCommandInput = {
    Bucket: process.env.AWS_BUCKET_NAME as string,
    Key: fileName,
    Body: fileBuffer,
    ContentType: file.type,
  };
  const command = new PutObjectCommand(params);
  await s3Client.send(command);
}

export const submitImage = async (formData: FormData) => {
  const image = formData.get('image');
  if (typeof image !== 'object' || !image || image?.size === 0) {
    return '';
  }
  // 영숫자가 아닌 문자를 영숫자로
  // 코드에서 관리하기 더 쉬운 파일명을 생성
  // eslint-disable-next-line no-control-regex
  const asciiName = image.name.replace(/[^\\x00-\\x7F]/g, '').replace('', '_');
  const fileName = `${new Date().getTime()}-${asciiName}`;
  await uploadFileS3(image, fileName);
  // TODO: DB work with URL
  const url = `${process.env.NEXT_PUBLIC_IMAGE_URL_END_POINT}/${fileName}`;
  return url;
};

client side form

'use client';

import { useState, useTransition } from 'react';
import { submitImage } from './serverAction';

const UploadForm = () => {
  // form action does not support re-rendering
  // during the action is in progress
  // unless it is rendered with 'useTransition'
  const [pending, onTransition] = useTransition();
  const [uploadState, setUploadState] = useState('idle');
  const [imgUrl, setImgUrl] = useState('');

  const onSubmit = async (formData: FormData) => {
    // client-side javascript validation is allowed.
    // use trigger() in react-hook-form
    const imgFile = formData.get('image') as File | null;
    if (!imgFile) return;
    const url = await submitImage(formData);
    if (url === '') {
      setUploadState('upload fail');
      return;
    }
    setUploadState('file uploaded');
    setImgUrl(url);
  };

  return (
    <form
      action={(formData: FormData) => {
        onTransition(() => {
          onSubmit(formData);
        });
      }}
      className="flex flex-col"
    >
      {!pending && <p>upload state: {uploadState}</p>}
      {pending && <p>uploading...</p>}
      {imgUrl && (
        <a href={imgUrl} target="_blank">
          {imgUrl}
        </a>
      )}
      <input type="file" name="image" />
      <button type="submit" disabled={pending}>
        upload
      </button>
    </form>
  );
};

export default UploadForm;

File uploading with Next.js 14 app route, AWS S3, Cloudfront for Dummies, Part 2