

- Share On
目次
目次
- Supabase Storageとは?手軽にファイル管理ができるクラウドストレージ
- Supabase Storageの主な特徴3つ
- 1. 大容量ファイルの保存と配信が可能
- 2. データベースと連携したアクセス制御
- 3. ファイルの変換・最適化を自動で実行
- Supabase Storageを使い始めるための初期設定
- ステップ1:Supabaseで新規プロジェクトを作成する
- ステップ2:ファイルを保存するバケットを用意する
- ステップ3:アプリケーションにSupabaseクライアントを導入する
- 【基本操作】Supabase Storageのファイル操作方法
- ファイルをアップロードする方法
- アップロードしたファイルの一覧を取得する方法
- 特定のファイルをダウンロードする方法
- 不要になったファイルを削除する方法
- セキュリティを強化するポリシー(RLS)の設定手順
- 非公開バケットで署名付きURLを使う(限定公開)
- サーバー経由アップロード(Route Handler/Server Actions)
- ファイル名衝突と上書き対策
- MIME/拡張子・サイズの検証(フロント + サーバー)
- Next.js Image最適化とリモート許可設定
- 変換・キャッシュ(表示速度のチューニング)
- メタデータ保存(誰の・どのファイルか)と削除運用
- Next.jsでの具体的な実装例(フロントに画像を表示するまで)
- ファイルをアップ→URL取得→その場で表示(公開バケット)
- 補足:非公開バケットを使いたい場合(署名URL)
- まとめ
Supabase Storageは、画像やPDF、動画を簡単に保存・配信できるクラウドストレージです。Next.jsなどのアプリと相性が良く、公開・非公開の切り替えや最適化も柔軟。
この記事では、基本操作から安全な運用方法までをわかりやすく解説します。
Supabase Storageとは?手軽にファイル管理ができるクラウドストレージ
最初にSupabase Storageの全体像をつかんでから、実装に入ると迷いが少なくなります。ここでは「何ができるか」をシンプルに押さえます。
アプリ専用のクラウドストレージとして、画像・PDF・動画等を保存できる
そのまま配信(CDN)でき、表示もスムーズ
データベース(Postgres)と統合され、アクセス制御を設計しやすい
ポイントは「フロントエンドからも扱いやすいAPI」と「DBと一体のセキュリティ設計」。この2つが運用の楽さに直結します。
Supabase Storageの主な特徴3つ
1. 大容量ファイルの保存と配信が可能
導入のハードルが低く、重めの画像や動画も運用可能です。
写真投稿・ECのサムネイル/詳細画像
セミナー動画/eラーニング教材
資料配布用PDF
補足:配信はCDN経由になるため、世界中のユーザーに対しても安定した速度で提供できます。数十MBを超える動画などは、標準のupload
よりもTUS(レジューム対応アップロード)を検討すると安心です。
2. データベースと連携したアクセス制御
ユーザーIDや商品IDなど、アプリのデータ構造と揃えて制御できます。
ログイン済みの本人だけ閲覧
チーム/組織単位での共有
購入済みユーザーのみアクセス 等
補足:この仕組みはRow-Level Security(RLS)で柔軟に表現します。後述の「セキュリティ」章で詳しく扱います。
3. ファイルの変換・最適化を自動で実行
画像のリサイズやWebP変換などをURLパラメータで呼び出すだけで適用できます。
サイト速度(LCP等)や転送量の削減に効果的です。
Supabase Storageを使い始めるための初期設定
ステップ1:Supabaseで新規プロジェクトを作成する
Supabaseにログイン → New Project
プロジェクト名・パスワード・リージョンを設定
数十秒でDB+Storageが起動
最初のうちは無料枠で十分に試せます。課金の前にプロトタイプで操作感を確認しましょう。
ステップ2:ファイルを保存するバケットを用意する
Storage → Buckets → New Bucket
公開(Public) or 非公開(Private)を選択
公開:URLが分かれば誰でも読める
非公開:原則読めない(署名URLなどで限定公開)
使い分けの目安
サイトで誰でも見られる画像:Public
ログイン後だけ閲覧したいファイル:Private
ステップ3:アプリケーションにSupabaseクライアントを導入する
npm i @supabase/supabase-js
// src/lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseKey);
補足:NEXT_PUBLIC_
が付くキーは公開前提。読み取り専用のAnon Keyに限って使い、Service Role Keyは絶対にクライアントへ出さない(サーバー専用)。
【基本操作】Supabase Storageのファイル操作方法
導入したら、まずは基本の4操作(アップロード/一覧/ダウンロード/削除)を試しましょう。流れが掴めると、UIの設計もイメージしやすくなります。
ファイルをアップロードする方法
使いどころ:フォームから画像を登録、管理画面でPDFを追加 など
const { data, error } = await supabase.storage
.from('public-images')
.upload('example.png', file); // 第2引数は File/Blob
if (error) console.error(error);
補足:upload(path, file, { upsert })
の upsert
を false
にしておくと、誤上書きを防げます。大容量ファイル(数十MB以上)はupload
ではなくTUS APIでの分割アップロードが推奨です。
アップロードしたファイルの一覧を取得する方法
使いどころ:管理画面のファイルブラウザ、ギャラリー表示
const { data, error } = await supabase.storage
.from('public-images')
.list('', { limit: 100, sortBy: { column: 'name', order: 'desc' } });
if (!error) data?.forEach((f) => console.log(f.name));
補足:公式ドキュメントではsortBy.column
はname
を指定するのが標準です。作成日時での並べ替えをしたい場合は、ファイル情報を別テーブルに保存して管理すると確実です。大量データはページング(limit
/offset
)で扱うとUIが軽くなります。
特定のファイルをダウンロードする方法
使いどころ:PDFを開く、画像を一時URL化してプレビュー
const { data, error } = await supabase.storage
.from('public-images')
.download('example.png');
if (data) {
const url = URL.createObjectURL(data);
// <img src={url} /> 等でプレビュー
}
補足:Publicバケットの場合は getPublicUrl
の方が手軽です。
不要になったファイルを削除する方法
使いどころ:差し替えや退会時の削除、週次の整理
const { error } = await supabase.storage
.from('public-images')
.remove(['example.png']);
補足:運用では「誰がいつアップしたか」をDBに残しておくと、棚卸しや一括削除が楽になります(後述)。
セキュリティを強化するポリシー(RLS)の設定手順
導入直後に必ず見直したいのがアクセス制御です。ここを曖昧にすると、意図しないアップロードや閲覧が発生しやすくなります。
前提整理(導入)
アクセス制御は「誰が」「どのバケットの」「何を(read/write)」できるかを切り分けて考えます。
チェックポイント(箇条書き)
公開バケットでも「書き込みは誰でもOK」にはならない(別途ポリシーが必要)
認証済みユーザーだけに
insert
許可非公開は署名URLかサーバー経由で配布
ユーザーIDでの絞り込み(自分のファイルのみ)を用意
補足(解説)
バケットの「公開/非公開」は読み取り(select)の初期方針に過ぎません。書き込み(insert/update/delete)は別管理です。スパム対策や誤操作防止のため、書き込みポリシーの明示は必須です。
非公開バケットで署名付きURLを使う(限定公開)
導入
会員限定・決済後限定のコンテンツ配布に有効です。有効期限付きURLを都度発行します。
ポイント
URLには期限(短め)を設定
都度発行するため、直リンク共有への抑止になる
失効後はアクセスできない
サンプル
const { data, error } = await supabase.storage
.from('user-files') // Privateバケット
.createSignedUrl('path/to/file.png', 60); // 60秒
if (data?.signedUrl) {
// <img src={data.signedUrl} />
}
サーバー経由アップロード(Route Handler/Server Actions)
導入
クライアント直書き込みは簡単ですが、最終バリデーションやPrivate運用を考えると、サーバーで受けてからStorageへが堅実です。
箇条書き
Service Role Keyはサーバー専用(公開NG)
MIME/サイズ検証をサーバー側で最終確認
ユーザーIDの紐づけとメタ情報のDB保存がしやすい
最小サンプル(POST /api/upload)
// app/api/upload/route.ts
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; // server only
const supabase = createClient(url, serviceKey);
export async function POST(req: Request) {
const form = await req.formData();
const file = form.get('file') as File | null;
if (!file) return NextResponse.json({ error: 'file required' }, { status: 400 });
if (!file.type.startsWith('image/')) {
return NextResponse.json({ error: 'invalid type' }, { status: 400 });
}
if (file.size > 5 * 1024 * 1024) {
return NextResponse.json({ error: 'file too large' }, { status: 400 });
}
const arrayBuffer = await file.arrayBuffer();
const key = `uploads/${crypto.randomUUID()}-${file.name}`;
const { error } = await supabase.storage
.from('user-files')
.upload(key, Buffer.from(arrayBuffer), {
contentType: file.type,
upsert: false,
});
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
const { data } = await supabase.storage
.from('user-files')
.createSignedUrl(key, 300); // 5分
return NextResponse.json({ url: data?.signedUrl, path: key });
}
ファイル名衝突と上書き対策
導入
Date.now()
は簡単ですが完全ではありません。UUID+ユーザーディレクトリが基本。箇条書き
crypto.randomUUID()
などでユニーク化upsert: false
で誤上書き防止/userId/yyyy/mm/uuid.ext
のような階層化で整理
補足
ディレクトリ設計は、棚卸し・課金・削除まで見据えると効きます。
MIME/拡張子・サイズの検証(フロント + サーバー)
導入
二段構えで安全性を担保。偽装MIMEや巨大ファイルを早期ブロック。
箇条書き
フロント:
accept="image/*"
と早期アラートサーバー:
file.type
とfile.size
を最終検証必要に応じてマルウェアスキャン等の外部連携
Next.js Image最適化とリモート許可設定
導入
next/image
を使うなら、Supabaseのストレージドメインを許可します。サンプル
// next.config.js
module.exports = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: '**.supabase.co', pathname: '/storage/v1/object/**' },
],
},
};
補足
sizes
やfill
を正しく指定し、不要な巨大画像配信を避けます。
変換・キャッシュ(表示速度のチューニング)
導入
画像の幅・品質・フォーマットを最適化し、CDNキャッシュで高速化。
箇条書き
?width=…&quality=…&format=webp
などの変換パラメータcache-control
を付与して再配信コストを削減
補足
サムネイル/詳細など用途ごとにサイズを決め打ちすると安定します。
メタデータ保存(誰の・どのファイルか)と削除運用
導入
files
テーブルを持ち、user_id / bucket / path / size / mime / created_at 等を記録。箇条書き
一覧表示・権限チェック・削除時に活用
「退会ユーザーの資産を一括削除」などの保守が楽に
最小DDL
create table public.files (
id uuid primary key default gen_random_uuid(),
user_id uuid not null,
bucket text not null,
path text not null,
size int8,
mime text,
created_at timestamptz default now()
);
Next.jsでの具体的な実装例(フロントに画像を表示するまで)
ここは“まず動かす”体験を重視した最短ルートです。そのうえで、上の安全対策を段階的に足していきましょう。
ファイルをアップ→URL取得→その場で表示(公開バケット)
導入
getPublicUrl
で即時に表示できます。まずはここから。コード
'use client';
import { useState } from 'react';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
const BUCKET = process.env.NEXT_PUBLIC_SUPABASE_BUCKET!;
export default function UploadImage() {
const [url, setUrl] = useState<string | null>(null);
const [msg, setMsg] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
async function onChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) return setMsg('画像のみアップ可能です。');
if (file.size > 5 * 1024 * 1024) return setMsg('5MB超はアップできません。');
setBusy(true); setMsg(null);
try {
const key = `public/${crypto.randomUUID()}-${file.name}`;
const { error } = await supabase.storage
.from(BUCKET)
.upload(key, file, { upsert: false, contentType: file.type, cacheControl: '3600' });
if (error) throw error;
const { data } = supabase.storage.from(BUCKET).getPublicUrl(key);
setUrl(data.publicUrl);
setMsg('アップロード成功');
} catch (err: any) {
setMsg(err?.message ?? 'アップロードに失敗しました');
} finally {
setBusy(false);
}
}
return (
<div>
<input type="file" accept="image/*" disabled={busy} onChange={onChange} />
{msg && <p style={{ marginTop: 8 }}>{msg}</p>}
{url && <img src={url} alt="" style={{ maxWidth: '100%', marginTop: 12 }} />}
</div>
);
}
補足
本番では上の「セキュリティ」セクションの対策を必ず追加してください。
補足:非公開バケットを使いたい場合(署名URL)
導入
会員限定や有料コンテンツなどでは、Privateバケット+署名URLが基本。
箇条書き
createSignedUrl(path, seconds)
で期限付きURLを発行クライアント直発行でも可だが、サーバーでの発行・検証がより安全
ダウンロード履歴や制限が必要ならサーバー経由を検討
サンプル(最小)
const { data } = await supabase.storage
.from('user-files')
.createSignedUrl('userId/2025-09/xxx.png', 120);
まとめ
Supabase Storageは、シンプルに始められて拡張もしやすいファイル管理の仕組みです。この記事で紹介した流れを意識すれば、運用の失敗を防ぎやすくなります。
最初から完璧を目指すより、「まず動かす → 少しずつ安全策を追加 → 実運用に耐えられる設計に整える」というステップを踏むと、より理解が進むと思います。
Contact
制作のご依頼やサービスに関するお問い合わせ、
まだ案件化していないご相談など、
お気軽にお問い合わせください。
- この記事をシェア