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 を追加

  1. Supabaseプロジェクトのダッシュボードにログイン

  2. 左側のメニューから「Storage」を選択する

  3. 「Policies」タブをクリックする

  4. Policy を与えたい Bucket の「New Policy」ボタンをクリックする

  5. 「For full customization」を選択する

  6. Policy name に適当な名前を入力する(ex: Give authenticated users access)

  7. 「Allowed operation」で SELECT を選択する

  8. 「Target roles」 で authenticated を選択する

  9. 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>
  );
}


いいなと思ったら応援しよう!