Nextjs + SupabaseでSupabaseのStorage Image Transformationsを利用する方法
- 土門 大貴
- 記事制作日2024年3月23日
- 更新日2024年3月23日
- 0いいね!
前回はSupabase上で『画像投稿アプリの最適化』方法についてご紹介しました。
しかしながらSupabaseでPRO以上のプランを契約しておられる場合、もっと簡単に画像の最適化を行う事ができます。
それが『Storage Image Transformations』です。
今回はそちらの実装方法をご紹介します。
※Storage Image Transformationsの機能はPROプラン以上(PRO・TEAM・ENTERPRISE)でないと利用できませんのでご注意ください。
Supabase側の準備
以前作成した記事と同じように準備していただければと思います。
Next.jsの実装
git clone したこちらのリポジトリをもとに作成します。
変更が必要なのは下記ファイルのみです。
components/sharedStorageApp.tsx
"use client";import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";import { useEffect, useState } from "react";import { v4 as uuidv4 } from "uuid";export default function ImageApp() { const public_url = `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/render/image/public/shared-bucket/shared-folder/`; const supabase = createClientComponentClient(); const [urlList, setUrlList] = useState<string[]>([]); const [loadingState, setLoadingState] = useState("hidden"); const diffOverMinute = (timestamp1: Date, timestamp2: Date) => { const difference = timestamp1.getTime() - timestamp2.getTime(); // 差が60000ミリ秒(= 1分)以上であるか確認 if (difference >= 60000) { return true; } else { return false; } }; const limitUploadCount = async () => { const { data, error } = await supabase.storage .from("shared-bucket") .list("shared-folder", { limit: 10, offset: 0, sortBy: { column: "created_at", order: "desc" }, }); if (error) { console.log(error); return; } if (data.length == 10) { const isDiffOverMinute = diffOverMinute( new Date(data[0].created_at), new Date(data[9].created_at) ); return isDiffOverMinute; } else { return true; } }; const listAllImage = async () => { const tempUrlList: string[] = []; setLoadingState("flex justify-center"); const { data, error } = await supabase.storage .from("shared-bucket") .list("shared-folder", { limit: 100, offset: 0, sortBy: { column: "created_at", order: "desc" }, }); if (error) { console.log(error); return; } for (let index = 0; index < data.length; index++) { if (data[index].name != ".emptyFolderPlaceholder") { tempUrlList.push(data[index].name); } } setUrlList(tempUrlList); setLoadingState("hidden"); }; useEffect(() => { (async () => { await listAllImage(); })(); }, []); const [file, setFile] = useState<File>(); const handleChangeFile = (e: any) => { if (e.target.files.length !== 0) { setFile(e.target.files[0]); } }; const onSubmit = async (event: any) => { event.preventDefault(); const isDiffOverMinute = await limitUploadCount(); // 直近1分間に画像を10ファイル以上アップロードさせない if (!isDiffOverMinute) { alert( "画像ファイルのアップロード制限がかかっています。時間をあけてアップロードしてください" ); return; } if (file!!.type.match("image.*")) { const fileExtension = file!!.name.split(".").pop(); const { error } = await supabase.storage .from("shared-bucket") .upload(`shared-folder/${uuidv4()}.${fileExtension}`, file!!); if (error) { alert("エラーが発生しました:" + error.message); return; } setFile(undefined); await listAllImage(); } else { alert("画像ファイル以外はアップロード出来ません。"); } }; return ( <> <form className="mb-4 text-center" onSubmit={onSubmit}> <input className="relative mb-4 block w-full min-w-0 flex-auto rounded border border-solid border-neutral-300 bg-clip-padding px-3 py-[0.32rem] text-base font-normal text-neutral-700 transition duration-300 ease-in-out file:-mx-3 file:-my-[0.32rem] file:overflow-hidden file:rounded-none file:border-0 file:border-solid file:border-inherit file:bg-neutral-100 file:px-3 file:py-[0.32rem] file:text-neutral-700 file:transition file:duration-150 file:ease-in-out file:[border-inline-end-width:1px] file:[margin-inline-end:0.75rem] hover:file:bg-neutral-200 focus:border-primary focus:text-neutral-700 focus:shadow-te-primary focus:outline-none" type="file" id="formFile" accept="image/*" onChange={(e) => { handleChangeFile(e); }} /> <button type="submit" disabled={file == undefined} className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center disabled:opacity-25" > 送信 </button> </form> <div className="w-full max-w-3xl"> <div className={loadingState} aria-label="読み込み中"> <div className="animate-spin h-10 w-10 border-4 border-blue-500 rounded-full border-t-transparent"></div> </div> <ul className="flex flex-wrap w-full"> {urlList.map((item, index) => ( <li className="w-1/4 h-auto p-1" key={item}> <a className="hover:opacity-50" href={public_url + item + "?quality=50&format=origin"} target="_blank" > <img className="object-cover max-h-32 w-full" src={ public_url + item + "?width=200&quality=50&resize=contain" } /> </a> </li> ))} </ul> </div> </> );}
変更点は下記になります。
URLをTransformation用に変更
const public_url = `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/render/image/public/shared-bucket/shared-folder/`;
Supabase側でのレンダリング後の画像を指定するよう変更しています。
画像表示部分
<aclassName="hover:opacity-50"href={public_url + item + "?quality=50&format=origin"}target="_blank"> <img className="object-cover max-h-32 w-full" src={ public_url + item + "?width=200&quality=50&resize=contain" } /></a>
中の画像(img)は横幅を200px、品質は50(半分)で横幅の変更に合わせてresizeを'contain'に変更しています。
※品質は20 ~ 100で指定可能で高いほど良い品質になります。
※resizeはcssのobject-fitのように指定可能です。
リンクに指定した画像URLは品質を50にしつつ、formatを'origin'としています。
※デフォルトだと画像の拡張子が最適なものに変換されてしまうため元の形式を保つためにformatの指定をします。
動作確認
共有ストレージに画像をアップロードしてみます。
https://www.pexels.com/photo/unrecognizable-tourist-standing-under-rough-cliff-in-mountains-during-vacation-3791466/
こちらの3MBほどある画像を実際にアップロードしてみました。
ブラウザのコンソールから見てみると表示されている画像は200pxまで幅を小さくしていたのもあり、約8kbまでファイルサイズが小さくなっています。
画像をクリックした際に表示されるリンク先も品質を半分にしたおかげで約1MBほどのサイズになっていました。
最後に
Supabaseには、アプリを作る上で『こういう機能があったらいいな』というものがたくさん用意されています。
今回の『Storage Image Transformations』のように、freeプランでは使えない機能もあるため、もし可能であればProプランの契約を検討してみてください。
Proプランは毎月25ドルかかりますが、内10ドルはコンピューティングクレジットとして使うことができるため、できることが大きく広がります。
その他参考資料など
またTodoONada株式会社では、この記事で紹介した以外にも、各種チャットシステムやストレージを利用した画像アプリの作成方法についてご紹介しています。
ぜひこちらもご覧ください!
- Next.js + SupabaseでTodoアプリ作成 CRUDの基本を学ぼう
- Next.js + Supabaseでリアルタイムチャットを作ろう
- Next.js + SupabaseでStorageを利用した画像投稿アプリ作成
- Next.js + SupabaseでRLSを利用して安全なアプリを作ろう。
- Next.js + SupabaseでAuth + Storageのストレージサービスを作る方法
- Next.jsとSupabaseで認証機能ありのリアルタイムチャットを作成する。
- Next.jsとSupabaseで認証つきチャットアプリを作成する(SNS風UI)
- Next.js + Supabaseでソーシャルログインを実装する方法
- Next.js + SupabaseでSMS認証を作成する方法
- Supabase + Next.jsで画像投稿アプリを最適化する(画像圧縮、ファイルサイズ制限、ファイルのアップロード数制限)
- Next.jsとSupabaseで全文検索を実装する方法
お問合せ&各種リンク
お問合せ:GoogleForm
ホームページ:https://libproc.com
運営会社:TodoONada株式会社
Twitter:https://twitter.com/Todoonada_corp
Instagram:https://www.instagram.com/todoonada_corp/
Youtube:https://www.youtube.com/@todoonada_corp/
Tiktok:https://www.tiktok.com/@todoonada_corp
presented by
- この記事にいいね!する
この記事を書いた人
- 29いいね!
稼働ステータス
◎現在対応可能
- 土門 大貴
職種
エンジニア
システムエンジニア(SE)
希望時給単価
10,000円~30,000円
▼実績例 ・公共インフラ事業者様向け管理システム開発(Windows、Python、PostgreSQL) ・官公庁様向け地図情報アプリのインフラ開発(Windows、PostgreSQL) ・自治体様向けポイント管理サービスのAPI開発(Linux、PostgreSQL、JS、Python) ・大手製造業様向けクラウド環境開発支援(AWS全般、Terraform) ・公共事業様向け顔認証決算システム基盤開発(Windows、PostgreSQL、JS、Python) ・リース業様向け代理店向けWebAPI開発(AWS全般、GoLang、JS) ・通販サイトインフラ構築支援、要件定義~開発(AWS, ECCube) ・結婚相談所様向けオウンドメディア制作(WordPress、JS、ウェブディレクトションな)
スキル
Python
AWS
React
・・・(登録スキル数:6)
スキル
Python
AWS
React
・・・(登録スキル数:6)