0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Next.js]Cloudinaryでasset_folderを動的に設定してみた

Posted at

はじめに

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に環境変数を設定

.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 uploadAvatarfileData: 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登録され、しばらくすると画像が自動更新される

image.png

さいごに

Widgetを使わずバックエンドSDKでアップロードする方が、実際には使うケースが多そうです。
無料で十分使えてこれだけ簡単に組み込めるので、Cloudinaryを今後も使っていこうと思います。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?