前回は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側でのレンダリング後の画像を指定するよう変更しています。
画像表示部分
<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>
中の画像(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