はじめに
Next.jsにチャレンジしていく過程をメモ/備忘録として記録していきます。
同じようにこれから始める方の参考となればと思います。
やりたいこと
CloudinaryのNode.js SDKを使って、ディレクトリ(asset_folder)をユーザー毎に設定してアップロードしたい
きっかけ
前回next-cloudinary
というライブラリを使って簡単に、widgetを使ってのアップロードを実装してみました。
widgetを使い簡単かつ便利に使えそうでした。
ただ、widgetがcloudinary感丸出しなのと、アプリ上でユーザー側からアップロードする場合、フォルダー(asset_folder)がユーザーID毎に分かれていた方が管理しやすそうだなと思いました。
uploadPresetで設定する方法以外を見つけきれなかったので、バックエンドSDKを使ってやってみます。
環境
下記のDocker開発環境にて行います。
Node.js SDKのアップロードについて
Node.js SDK は、CloudinaryのアップロードAPI をラップしており、より簡単にアップロードできるようになっていました。
基本的にアップロードは下記だけです。
function upload(file, options).then(callback)
そこに設定情報も加え実際に使用する形にしていくと
1.まずは.env
に環境変数を設定
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="<Your Cloud Name>"
NEXT_PUBLIC_CLOUDINARY_API_KEY="<Your API Key>"
CLOUDINARY_API_SECRET="<Your API Secret>"
2.upload用関数を作成
cloudinary.config
に.env
に設定した環境変数を読み込み、
それを使ってcloudinary.uploader.upload
を実行
アップロード先のフォルダをオプションにて設定できます。
その他にも多数のオプションがあります。
"use server";
import { v2 as cloudinary } from "cloudinary";
cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export async function uploadImage(fileData:string, asset_folder:string) {
const options = {
asset_folder: asset_folder,
}
const result = await cloudinary.uploader.upload(fileData,options);
console.log(result);
}
こちらは下記のようなレスポンスが返ってきます。
{
public_id: 'cr4mxeqx5zb8rlakpfkg',
version: 1571218330,
signature: '63bfbca643baa9c86b7d2921d776628ac83a1b6e',
width: 864,
height: 576,
format: 'jpg',
resource_type: 'image',
created_at: '2017-06-26T19:46:03Z',
bytes: 120253,
type: 'upload',
url: 'http://res.cloudinary.com/demo/image/upload/v1571218330/cr4mxeqx5zb8rlakpfkg.jpg',
secure_url: 'https://res.cloudinary.com/demo/image/upload/v1571218330/cr4mxeqx5zb8rlakpfkg.jpg'
}
アプリへ導入
1.ファイル選択用のinputを設置
・ファイルダイアログで選択出来るように<input type="file" />
を設置します。
・そのままだと、「ファイルを選択〜」という形になってしまうので、inputはhidden属性とします。
・その代わりのボタンを配置し、onClickの処理でinputをクリックするようにしました。
const inputRef = useRef<HTMLInputElement>(null);
<div className='relative left-10 bottom-8'>
<div
onClick={() => inputRef.current?.click()}
className="btn w-11 h-11 rounded-full bg-slate-700/70 hover:bg-slate-500/70"
>
<CameraIcon width={24} height={24} addClass='fill-slate-400'/>
</div>
<input
type="file"
className='hidden'
ref={inputRef}
onChange={handleChange}
/>
</div>
2.base64へエンコードして関数実行
・cloudinaryへアップロードする時のファイルソースがBase64に対応しているため、変換してアップロードします。
const handleChange = async () => {
const file = inputRef.current?.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
if(!reader.result){
console.log('ファイルの読み込みに失敗しました。');
return;
}
uploadAvatar(reader.result as string, projectDetail.id);
router.refresh();
};
reader.readAsDataURL(file);
}
Cloudinaryへアップロードした後、
secure_url
をレコードのimage_url
へ登録
function uploadAvatar(fileData: string, projectId: string){
// Cloudinaryにアップロード
const results = await uploadImage(fileData, projectId);
// データベースに登録
// Some action ・・・
// results.secure_urlをimage_urlへ登録
}
Cloudinaryの登録時は、
・asset_folderをユーザー毎に
・public_id(オブジェクト名)はプロジェクトID
としました。
容量節約のため、画像を変更すると毎回上書きされるようにしました。
"use server";
import { getUser } from "@/features/user/actions";
import { v2 as cloudinary } from "cloudinary";
cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});
export async function uploadImage(fileData:string, public_id:string = '') {
const user = await getUser(); // ユーザー情報を取得
const options = {
asset_folder: 'tms-app/' + user.id,
public_id: public_id,
}
return await cloudinary.uploader.upload(fileData,options);
}
3.画像を更新して表示
当初はDB側にはpublic_idのみを持たせてようと考えていましたが、
毎回同じ名前で上書きされるようにしたいので、そのままだとブラウザのキャッシュにより画像が更新されませんでした。
Cloudinaryのsecure_url
はアップロードする度に変わるv〇〇〇〇〇の番号が入っているので
そのまま格納することにしました。
import defaultImg from '@/public/images/project/default.jpeg';
イメージ登録前は、publicディレクトリに置いた画像を表示しています。
useEffectを使って、DBにURLが登録されると表示画像も更新されるようにしました。
const [imageSrc, setImageSrc] = useState<string | undefined>(defaultImg.src);
useEffect(() => {
if(projectDetail.image_url){
setImageSrc(projectDetail.image_url);
}
}, [projectDetail.image_url]);
urlで格納しているため画像表示もCldImage
は使わずimgタグを使いました。
<div className="avatar">
<div className="w-32 rounded-full">
<img
src={imageSrc}
width="150"
height="150"
alt="avatar"
/>
</div>
</div>
テスト
無事、下記の流れで画像登録+表示更新されることが確認できました。
1.画像変更(カメラ)ボタンを押す
2.ダイアログが開くのでファイルを選ぶ
3.Cloudinaryにアップロード + DBへURL登録され、しばらくすると画像が自動更新される
さいごに
Widgetを使わずバックエンドSDKでアップロードする方が、実際には使うケースが多そうです。
無料で十分使えてこれだけ簡単に組み込めるので、Cloudinaryを今後も使っていこうと思います。
参考