🏞️

とりあえずNext.jsでS3を使って画像アップロード機能をつくってみる

に公開

S3をあまり触ったことがない方に向けて、何でもいいから最速でNext.jsからS3に画像をアップロードして、その画像を表示させるところまでやってみようという感じです。


下に表示される画像はS3上にアップロードされたファイルのリンクで表示させています。

適当にNext.js プロジェクトを作成

(※どちらでもいいですが今回はAppRouterにします)

npx create-next-app s3-upload-app --typescript

AWS側でS3の設定を行う

名前はなんでもいいのでS3でバケットを作成します。

またアップした画像ですが、アプリ側で表示できるようにしたいため、公開設定をパブリックに変更しておきます。

バケット名をクリックして「アクセス許可」→「バケットポリシー」に以下を記述します。
(Resourceの部分は作成したバケット名に変更してください)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your_bucket_name/*"
        }
    ]
}

以上でS3の設定は終わりです。
S3にアクセスするためにIAMが必要なのでこちらの設定もしていきます。

アプリからS3にアクセスするためにIAMの設定を行う

IAMで適当なユーザーを作成してポリシーを設定します。
「ユーザーの作成」から「ポリシーを直接アタッチする」を選択して「AmazonS3FullAccess」を選択します。(今回はフルアクセスでやっていますが必要に応じて変更してください)

作成したユーザーをクリックして「アクセスキーを作成する」を選択します。
++コマンドラインインターフェイス (CLI)**を選択してアクセスキーを作成します。

この時に取得できるアクセスキーシークレットアクセスキーをメモしておきます。

設定した内容を.env.localに認証情報を保存

.env.local
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=your_region
S3_BUCKET_NAME=your_bucket_name
Head Head
AWS_ACCESS_KEY_ID IAMで作成したユーザーのアクセスキー
AWS_SECRET_ACCESS_KEY IAMで作成したユーザーのシークレットアクセスキー
AWS_REGION S3のAWSリージョン(例: 東京の場合はap-northeast-1)
S3_BUCKET_NAME S3で作成したバケット名

ページとAPIを作成

作成したNext.jsのプロジェクトに以下を記述します。

app/page.tsx
'use client';

import { useState } from 'react'

export default function Home() {
  const [imageUrl, setImageUrl] = useState<string | null>(null)

  const handleUpload = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const fileInput = e.currentTarget.file as any
    const file = fileInput.files[0]
    const formData = new FormData()
    formData.append('file', file)

    const res = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    })

    const data = await res.json()
    setImageUrl(data.url)
  }

  return (
    <div className="m-12">
      <form onSubmit={handleUpload} className="">
        <input 
          type="file" 
          name="file" 
          accept="image/*" 
          required 
          className="file:mr-4 file:py-2 file:px-4 file:rounded-md file:text-sm file:bg-gray-300 file:text-black file:cursor-pointer" 
        />
        <button 
          type="submit" 
          className="block mt-8 py-2 px-6 bg-sky-600 rounded-full cursor-pointer text-white">
          アップロード
        </button>
      </form>

      {imageUrl && (
        <div className="mt-6">
          <p>アップロードされた画像</p>
          <img src={imageUrl} alt="Uploaded" width={300} />
        </div>
      )}
    </div>
  )
}

app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const s3 = new S3Client({
  region: process.env.AWS_REGION!,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

export async function POST(req: NextRequest) {
  const formData = await req.formData()
  const file = formData.get('file') as File

  if (!file) {
    return NextResponse.json({ error: 'No file provided' }, { status: 400 })
  }

  const arrayBuffer = await file.arrayBuffer()
  const buffer = Buffer.from(arrayBuffer)

  const key = `uploads/${Date.now()}-${file.name}`

  const params = {
    Bucket: process.env.S3_BUCKET_NAME!,
    Key: key,
    Body: buffer,
    ContentType: file.type,
  }

  try {
    await s3.send(new PutObjectCommand(params))
    const url = `https://${process.env.S3_BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`
    return NextResponse.json({ url })
  } catch (error) {
    console.error(error)
    return NextResponse.json({ error: 'Upload failed' }, { status: 500 })
  }
}

以上で実装は完了です。
これでNext.jsのアプリ上からS3に画像をアップロードすることができました!

Discussion