Next.js で Supabase の Private Bucket のリンクを取得する方法
はじめに
Supabase のストレージ機能を使用する際、セキュリティの観点から Private Bucket を使用するケースが多いと思います。今回は、Next.js アプリケーションで Supabase の Private Bucket から安全にファイルの URL を取得する方法について解説します。
前提
"next": "14.2.5",
"@supabase/ssr": "0.4.0",
"@supabase/supabase-js": "2.45.0",
Supabase クライアントの設定
// src/lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export function createClient() {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL || '',
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '',
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
for (const { name, value, options } of cookiesToSet) {
cookieStore.set(name, value, options);
}
} catch (error) {
console.error('Failed to set cookies in Server Component:', error);
throw new Error(
'Failed to set cookies in Server Component. This may indicate an issue with server-side rendering.',
);
}
},
},
},
);
}
Supabase のコンソールで Policy を追加
Supabaseプロジェクトのダッシュボードにログイン
左側のメニューから「Storage」を選択する
「Policies」タブをクリックする
Policy を与えたい Bucket の「New Policy」ボタンをクリックする
「For full customization」を選択する
Policy name に適当な名前を入力する(ex: Give authenticated users access)
「Allowed operation」で SELECT を選択する
「Target roles」 で authenticated を選択する
Policy definition で ((bucket_id = 'バケット名'::text) AND (auth.role() = 'authenticated'::text)) と入力して、 Review をクリックして、問題なければ、設定できる(バケット名は自分が作成したバケットの名前を入れてください)
バケットの URL を取得する関数を作成する
自分が作った bucket では audio ファイルを扱っていましたが、よしなに変更してください。
import { createClient } from '@/lib/supabase/server';
type Bucket = {
name: string;
url: string;
}
export async function getBucket(): Promise<{ data: Bucket[] }> {
const supabase = createClient();
const { data: files } = await supabase.storage.from('bucket-name').list();
if (!files || files.length === 0) {
return { data: [] };
}
const buckets = await Promise.all(
files.map(async (file) => {
const { data: urlData, error: urlError } = await supabase.storage
.from('buckets')
.createSignedUrl(file.name, 3600);
if (urlError) {
console.error(`Error creating signed URL for ${file.name}:`, urlError);
return null;
}
return {
name: file.name,
url: urlData.signedUrl,
};
}),
);
const validBuckets = buckets.filter((audio): audio is NonNullable<typeof audio> => audio !== null);
return { data: validBuckets };
}
以下のように page などの Server Component で取得して使用しましょう。
import { getBuckets } from '@/repositories/get-buckets';
import { CreateCustomAudioForm } from '@/components/create-custom-audio-form';
export default async function Page(): Promise<JSX.Element> {
const { data: buckets } = await getBuckets();
return (
<div className="container mx-auto px-4 py-8">
<h2 className="mb-6 font-bold text-2xl">カスタムオーディオの作成</h2>
<CreateCustomAudioForm buckets={buckets} />
</div>
);
}