はじめに
Next.jsにチャレンジしていく過程をメモ/備忘録として記録していきます。
同じようにこれから始める方の参考となればと思います。
やりたいこと
学習用に簡単なTodoアプリを作っていて、アプリ内で利用する画像を
Cloudinaryで管理したい
環境
下記のDocker開発環境にて行います。
Cloudinaryについて
クラウドベースのメディア管理プラットフォームで、主に画像や動画のアップロード、保存、最適化、配信を行うことができるサービス。
無料プランでも十分に使え、自動最適化やSDKも提供されている、魅力的なサービスです。
今回は使いませんが、AIによるオートクロッピングや顔認識の機能もあるようで、いつか使ってみたいと思います。
↓詳細はこちら↓
https://cloudinary.com/developers
導入
Next.js用のライブラリがあり、こちらの内容に沿って進めていきます。
1. サインアップ
「SIGN UP FOR FREE」からアカウントを作成
2. インストール
Next.js用のSDKが提供されているのでインストール
npm i next-cloudinary
3. envへ設定
.env.local
や .env
ファイルへ自分のAPIキーなどの情報を記載します。
情報はcoudinaryダッシュボードの「Product Environment」から取得できます。
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="<Your Cloud Name>"
NEXT_PUBLIC_CLOUDINARY_API_KEY="<Your API Key>"
CLOUDINARY_API_SECRET="<Your API Secret>"
4. サンプル画像でテスト
とりあえず、getting-startedに書いているものでテスト。
<div className="avatar">
<div className="w-32 rounded-full">
<CldImage
src="samples/animals/three-dogs" // Use this sample image or upload your own via the Media Explorer
width="150" // Transform the image: auto-crop to square aspect_ratio
height="150"
alt='avatar'
/>
</div>
</div>
CldImageコンポーネントで簡単に表示出来ました。
5. 署名付きアップロード用のAPIエンドポイントを作成
署名付きアップロードは CldUploadWidget
にAPIエンドポイントをプロパティとして渡すだけで実現出来ます。
Cloudinary Node SDKを使って、エンドポイントを作成します。
まずはSDKをインストール
npm install cloudinary
次にエンドポイントのroute.tsファイルを作成
import { v2 as cloudinary } from "cloudinary";
cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export async function POST(request: Request) {
const body = await request.json();
const { paramsToSign } = body;
const signature = cloudinary.utils.api_sign_request(paramsToSign, process.env.CLOUDINARY_API_SECRET);
return Response.json({ signature });
}
6. クライアントコンポーネントを作成
①CldUploadWidget
基本はこの形でwidgetが開き、署名付きでアップロードができるようになっています。
一方的にcloudinaryにアップロードするだけなのでもう少し手を加えます。
<CldUploadWidget signatureEndpoint="/api/sign-cloudinary-params">
{({ open }) => {
return (
<button onClick={() => open()}>
Upload an Image
</button>
);
}}
</CldUploadWidget>
②成功時の処理追加
widgetにonSuccessを追加して成功時の処理を追加。
onSuccess={onSuccessHandler}
下記のようなレスポンスが返ってくるので、レコードにpublic_idの情報を取り出して追加。
{
"event": "success",
"info": {
"id": "test1234",
"batchId": "test1234",
"asset_id": "abcdefghijklmnopqrstuvwxyz0123456789",
"public_id": "test1234",
"version": 123456789,
"version_id": "abcdefghijklmnopqrstuvwxyz0123456789",
"signature": "abcdefghijklmnopqrstuvwxyz0123456789",
"width": 100,
"height": 100,
"format": "png",
"resource_type": "image",
"created_at": "2024-08-17T12:00:26Z",
"tags": [],
"bytes": 1234,
"type": "upload",
"etag": "abcdefghijklmnopqrstuvwxyz0123456789",
"placeholder": false,
"url": "http://test1234/test1234/test1234/test1234.png",
"secure_url": "http://test1234/test1234/test1234/test1234.png",
"asset_folder": "test1234",
"display_name": "test1234",
"api_key": "123456789",
"path": "test1234/test1234/test1234/test1234.png",
"thumbnail_url": "https://test1234/test1234/test1234/test1234/test1234/test1234.png"
}
}
const onSuccessHandler = (results:CloudinaryUploadWidgetResults) => {
if(results.info && typeof results.info === 'object' && 'public_id' in results.info){
someUploadAction(results.info.public_id);
}else{
console.log('something wrong:', results);
}
}
③エラー時の処理を追加
onErrorでエラー処理を追加。
今回は省略してコンソールに表示にしています。
onError={onErrorHandler}
const onErrorHandler = (error: CloudinaryUploadWidgetError) => {
if (typeof error === 'object' && error?.statusText) {
console.error(error.statusText);
} else {
console.error(error);
}
}
④エラー時の処理を追加
onQueuesEndで終了後にwidgetを閉じるように追加。
onQueuesEnd={(results, { widget }) => {
widget.close();
}}
⑤uploadPresetを設定
cloudinary > setting > Upload Presets にて
アップロードのオプションのセットを一元的に定義して作成しておけます。
署名付きにするか署名なしにするか、PublicIDのネーミングルールなど予め設定しておきます。
https://cloudinary.com/documentation/upload_presets
作成したプリセットをwidgetへ追加。
uploadPreset="test1234"
⑥まとめるとこうなりました
"use client";
import { ActionState, ObjectDetail } from '../type'
import {
CldImage,
CldUploadWidget,
CldUploadWidgetPropsChildren,
CloudinaryUploadWidgetError,
CloudinaryUploadWidgetResults
} from 'next-cloudinary';
import CameraIcon from '@/components/icons/svg/CameraIcon';
import { toast } from 'react-toastify';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import defaultImg from '@/public/images/object/default.jpeg';
export default function AvatarForm({someObject}:{someObject:ObjectDetail}) {
const router = useRouter();
const AvatarImage = () => {
if (someObject.image_url){
return (
<CldImage
src={someObject.image_url}
width="150"
height="150"
alt='avatar'
/>
);
}else{
return (
<Image
src={defaultImg}
width="150"
height="150"
alt="avatar"
/>
);
}
}
const uploadAvatar = async (public_id: String) => {
someUploadAction(results.info.public_id);
router.refresh();
}
const onSuccessHandler = (results:CloudinaryUploadWidgetResults) => {
if(results.info && typeof results.info === 'object' && 'public_id' in results.info){
uploadAvatar(results.info.public_id);
}else{
console.log('something wrong:', results);
}
}
const onErrorHandler = (error: CloudinaryUploadWidgetError) => {
if (typeof error === 'object' && error?.statusText) {
console.error(error.statusText);
} else {
console.error(error);
}
}
const widgetChildren = ({ open }: CldUploadWidgetPropsChildren) => {
return (
<button
onClick={() => open()}
className="btn btn-xs text-sm hover:underline w-11 h-11 rounded-full bg-slate-700/70 hover:bg-slate-500/70"
>
<CameraIcon width={24} height={24} addClass='fill-slate-400'/>
</button>
);
}
return (
<div className='text-center'>
<div className="avatar">
<div className="w-32 rounded-full">
<AvatarImage />
</div>
</div>
<div className='relative left-10 bottom-8'>
<CldUploadWidget
signatureEndpoint="/api/sign-cloudinary-params"
uploadPreset="test1234"
onSuccess={onSuccessHandler}
onError={onErrorHandler}
onQueuesEnd={(results, { widget }) => {
widget.close();
}}
>
{widgetChildren}
</CldUploadWidget>
</div>
</div>
)
}
テスト
無事ボタンを押すとウィジェットが表示され、アップロードすることができました。
ファイルだけではなく、カメラやGoogleDriveからのアップロードもウィジェットについており、重宝しそうです。


さいごに
WEBのコンソール、widgetもかなり使いやすく、便利なサービスでした。
今後はドキュメントに書かれた内容だけでなく、APIをもっと使いこなしていけたらと思います。
参考