전체 소스코드
https://github.com/kimfame/next-s3/tree/37e1bfa31cbb54c87aecad1b1bc0e09f0a57c388
사전 작업
$ yarn add @aws-sdk/client-s3 uuid
- @aws-sdk/client-s3 : AWS SDK S3
- uuid : UUID 생성 (랜덤 이미지 이름 생성 목적 / uniqid 사용해도될 듯)
환경변수
/.env.local
AWS_REGION=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_S3_BUCKET_NAME=
환경변수 추가 및 외부 이미지 호출 설정
/next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
AWS_S3_OBJECT_URL: `https://${process.env.AWS_S3_BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com`,
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: `*.s3.${process.env.AWS_REGION}.amazonaws.com`, // 또는 '**.amazonaws.com'
},
],
},
}
export default nextConfig
- S3 이미지 호출 URL을 env 변수들을 조합하여 환경변수를 재생성하였는데 소스 상에 바로 입력해도 무방
- next.js에서 외부 이미지를 불러올 때는 호출 도메인을 명시해줘야한다. 아니면 아래와 같은 에러가 발생한다.
Unhandled Runtime Error
Error: Invalid src prop (https://bucket_name.s3.ap-northeast-2.amazonaws.com/file_name.png) on `next/image`, hostname "bucket_name.s3.ap-northeast-2.amazonaws.com" is not configured under images in your `next.config.js`
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host
- hostname은 '**.amazonaws.com'으로도 변경 가능
업로드폼
/src/components/UploadForm.jsx
'use client'
import Image from 'next/image'
import { useState } from 'react'
export default function Home() {
const [file, setFile] = useState(null)
const [uploading, setUploading] = useState(false)
const [imageUrl, setImageUrl] = useState('')
function handleFileChange(event) {
setFile(event.target.files[0]) // 다중 파일 시, 첫번째 파일만 선택
}
async function handleSubmit(event) {
event.preventDefault()
if (!file) return
setUploading(true)
const formData = new FormData()
formData.append('file', file)
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const data = await response.json()
setImageUrl(data)
setUploading(false)
} catch (error) {
setUploading(false)
}
}
return (
<>
<h1>Upload Images to AWS S3</h1>
<form onSubmit={handleSubmit}>
<input type="file" accept="image/*" onChange={handleFileChange} />
<button type="submit" disabled={!file || uploading}>
{uploading ? 'Uploading...' : 'Upload'}
</button>
</form>
{imageUrl && (
<Image src={imageUrl} width="300" height="300" alt="Image" />
)}
{!imageUrl && <h2>No image</h2>}
</>
)
}
- 이미지 업로드 후 양식 아래에 이미지 출력
업로드 양식 호출하는 상위 컴포넌트
/src/app/page.jsx
import UploadForm from '@/components/UploadForm'
export default function Home() {
return (
<main>
<UploadForm />
</main>
)
}
- 단순 컴포넌트 호출. 큰 의미없음.
S3 업로드 API
/src/app/api/upload/route.js
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
const s3Client = new S3Client({
region: process.env.AWS_REGION,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
async function uploadFileToS3(file, fileName, fileType) {
const key = fileName
const body = file
const contentType = fileType
const parmas = {
Bucket: process.env.AWS_S3_BUCKET_NAME,
Key: key,
ACL: 'public-read',
ContentType: contentType,
Body: body,
}
const command = new PutObjectCommand(parmas)
await s3Client.send(command)
}
export async function POST(req) {
try {
const formData = await req.formData()
const file = formData.get('file')
if (!file) {
return NextResponse.json({ error: 'File is required.' }, { status: 400 })
}
const fileExtension = file.name.split('.').slice(-1)[0] // 파일 확장자
const newFileName = `${uuidv4()}.${fileExtension}` // UUID와 기존 파일 확장자 붙여 새로운 파일 이름 생성
const fileType = file.type // 파일 종류
const buffer = Buffer.from(await file.arrayBuffer())
await uploadFileToS3(buffer, newFileName, fileType)
const imageUrl = `${process.env.AWS_S3_OBJECT_URL}/${newFileName}`
return NextResponse.json(imageUrl)
} catch (error) {
return NextResponse.json({ error })
}
}
- uploadFileToS3 함수 await 필수
await문 빼먹고 테스트하다가 S3 문제인 줄 알고 삽질 오지게 함.
소스 흐름도가 S3 업로드하고 이미지 URL을 가지고 화면에 바로 뿌려주고 있는데 await문을 안쓸 경우, 403 Forbidden 에러 발생
(S3에 업로드 진행 중인데 어플리케이션은 이미지 URL 선호출)
HTTP 에러 코드 상 400 에러가 날 것 같지만 테스트해보니 S3 상의 모든 미존재 파일 호출은 403 에러로 떨어진다.
- 다중 파일 업로드 시, 변경된 예시 코드
const formData = await request.formData()
const files = formData.getAll('file')
const response = await Promise.all(
files.map(async (file) => {
const fileExtension = file.name.split('.').slice(-1)[0]
const newFileName = `${uuidv4()}.${fileExtension}`
const fileType = file.type
const buffer = Buffer.from(await file.arrayBuffer())
uploadFileToS3(buffer, newFileName, fileType)
})
)
[출처]
https://www.youtube.com/watch?v=nGoSP3MBV2E
https://www.youtube.com/watch?v=inzUefvk2RE
https://medium.com/@antoinewg/upload-a-file-to-s3-with-next-js-13-4-and-app-router-e04930601cd6
https://github.com/RaddyTheBrand/NextJs-14-S3-Upload/blob/main/src/app/components/S3UploadForm.jsx
'React > Next.js' 카테고리의 다른 글
[Next.js] AWS S3 ACL Private 특정 유저만 접근 허용 (0) | 2024.03.28 |
---|---|
[Next.js] AWS S3 이미지 업로드 예제 (2) - presigned URL (0) | 2024.03.25 |
[Next.js] 아이디, 비밀번호 로그인 예제 (0) | 2024.03.18 |
[Next.js] 프로젝트 생성 / 개발 서버 실행 (0) | 2024.03.11 |
[Next.js] 개발 공부 (0) | 2024.03.11 |