メインコンテンツへスキップ
データベース・RLS
Supabase
Next.js
TypeScript
パフォーマンス
コスト最適化
セキュリティ

Supabase Storage 実装ガイド:アップロード経路の選定・署名URL・CDN・画像変換を本番品質で使う

Supabase Storageを本番で使い切るための実装ガイド。6MBを境にした標準アップロードとTUS再開可能アップロードの使い分け、サーバーを経由しない署名付きアップロードURL、S3互換エンドポイント、公開URLと署名URLの選択、Smart CDNのキャッシュ挙動と60秒の伝播、画像変換の上限とコスト、そして転送量(egress)を桁で下げる設計まで、公式ドキュメント準拠の実コードで解説します。

公開日
読了時間
18分
著者
友田 陽大
シェア

Supabase Storageは、upload() の1行でファイルが保存できます。だから多くのプロジェクトがその1行のまま本番に出て、次の3つで詰まります。

  1. 大きなファイルが途中で切れてやり直しになる(標準アップロードは再開できない)
  2. アップロードのたびにサーバー関数を経由して、実行時間と転送量を二重に払っている
  3. 画像をそのまま配って、転送量の請求が跳ねる

この記事は、Supabase Storageを本番品質にするための実装ガイドです。公式ドキュメント(2026-08-16時点)に忠実に、4つのアップロード経路の選び方、サーバーを経由しない署名付きアップロード、配信とCDNの挙動、画像変換の上限とコスト、そして転送量を桁で下げる設計までを、そのまま使えるコードで扱います。

この記事が扱わないこと:Storageのアクセス制御storage.objects へのRLS、storage.foldername(name) によるユーザー別フォルダ、所有者スコープ)は、Supabase StorageをRLSで守るで1本まるごと扱っています。本記事はRLSが正しく設計されている前提で、その上のアップロード・配信・コスト設計に集中します。


1. モデルとバケット:3種類ある

Supabase Storageのバケットは現在3種類あります。

種類用途(公式の定義)
Files buckets「画像・動画・ドキュメント・汎用ファイルを保存し配信する」
Analytics buckets「Apache Icebergテーブルでデータを保存する。データレイク、ログ、Supabase Pipelines向け」
Vector buckets「埋め込みを保存し類似検索を実行する。セマンティックマッチング、AI、RAG向け」

この記事が扱うのは Files buckets です。特徴は次の通りです。

  • 「S3互換ストレージ、RESTful API、TUS再開可能アップロード」
  • 「世界285以上の都市から高速に配信」
  • 「オンザフライでのリサイズ・圧縮・変換」
  • 「行レベルセキュリティとカスタムポリシーでファイル権限を管理」

最後の1行が本質です。Supabase Storageのファイルは storage.objects テーブルの行であり、フォルダは仮想(パス文字列の一部)です。だからアクセス制御がRLSで書ける。この設計を理解していれば、後の判断がすべて素直につながります。

バケットの作成と制限

const { data, error } = await supabase.storage.createBucket('avatars', {
  public: true,
  allowedMimeTypes: ['image/*'],
  fileSizeLimit: '1MB',
})

公式の説明では、この設定で「ユーザーが avatars バケットに画像だけをアップロードでき、サイズは1MBを超えられない」ようにできます。制限に合わないアップロードは拒否されます。

グローバルのファイルサイズ上限はプランで決まります。

プラングローバル上限
Free50 MB
Pro500 GB
Team500 GB
Enterpriseカスタム

バケット単位の上限はグローバル上限を超えられません。公式の推奨はこうです。

グローバル上限は、アプリケーションが受け入れる最大のファイルサイズに設定し、必要に応じてバケットごとにより小さい上限を設定するべき。

設計としてはこう読みます:グローバルは「システム全体の天井」、バケットは「用途ごとの現実的な上限」。avatars に500GBのファイルを送れる状態を放置しない、ということです。これは単なる制限ではなく、転送量コストの防波堤でもあります。


2. アップロード経路の選定:4つある

ここが最初の設計判断です。

経路使う場面実装
標準アップロード6MB以下の小さいファイルsupabase.storage.from(...).upload()
TUS 再開可能アップロード6MB超、不安定な回線、進捗表示が要るtus-js-client / Uppy
署名付きアップロードURLサーバーが許可判断をし、バイト列はクライアントから直送したいcreateSignedUploadUrl() + uploadToSignedUrl()
S3互換エンドポイント既存のS3ツールチェーン(AWS SDK、CLI、バックアップツール)があるAWS SDK S3Client

2-1. 標準アップロード

const { data, error } = await supabase.storage
  .from('bucket_name')
  .upload('file_path', file, {
    // 既存ファイルを上書きする。false(既定)なら 400 Asset Already Exists
    upsert: false,
    // 省略時、Storageは拡張子からContent-Typeを推測する
    contentType: 'image/jpeg',
  })

公式の位置づけはこうです。

標準のファイルアップロードは、6MBを超えない小さいファイルに最適。

標準アップロードでも最大5GBまで送れますが、再開できません。公式は「より高い信頼性のため、6MBを超えるファイルにはTUS再開可能アップロードの使用を推奨」としています。

contentType は省略できますが、省略しないほうがよいです。拡張子からの推測に頼ると、拡張子のないファイルや偽装された拡張子で意図しない型が付きます。そして「クライアントが宣言した contentType は信用できる値ではない」ことも同時に覚えておいてください——バケットの allowedMimeTypes と、必要ならサーバー側の実バイト検査で二重に守ります。

2-2. TUS 再開可能アップロード

Supabase StorageはTUSプロトコルを実装しています。tus-js-client または Uppy から使えます。

var upload = new tus.Upload(file, {
    // 直接のストレージホスト名を使う(パフォーマンスのため)
    endpoint: `https://${projectId}.storage.supabase.co/storage/v1/upload/resumable`,
    retryDelays: [0, 3000, 5000, 10000, 20000],
    headers: {
        authorization: `Bearer ${session.access_token}`,
        'x-upsert': 'true',
    },
    uploadDataDuringCreation: true,
    removeFingerprintOnSuccess: true,
    metadata: {
        bucketName: bucketName,
        objectName: fileName,
        contentType: 'image/png',
        cacheControl: '3600',
        metadata: JSON.stringify({yourCustomMetadata: true}),
    },
    chunkSize: 6 * 1024 * 1024,
    onError: function (error) {},
    onProgress: function (bytesUploaded, bytesTotal) {},
    onSuccess: function () {},
})

絶対に守る制約が1つあります。公式の表現をそのまま引きます。

チャンクサイズは6MBに設定しなければならない(現時点では)。変更しないこと。

もう1つ、同時実行の挙動です。同じアップロードURLに同時にアップロードできるクライアントは1つだけで、同一パスに複数のクライアントがアップロードすると 409 Conflict が返ります。最初に完了したものが成功します(x-upsert ヘッダを付けた場合は最後のもの)。

この409は、UIで「アップロードに失敗しました」と出して終わらせてはいけないエラーです。「同じファイルを別の端末でアップロード中」という状態を意味するので、ユーザーに何が起きているかを伝える必要があります。

2-3. 署名付きアップロードURL:サーバーを細くする

「アップロードの許可判断はサーバーでしたいが、バイト列はサーバーを通したくない」——これが最も実用的なパターンです。サーバー関数の実行時間・メモリ・転送量を消費せずに済みます。

createSignedUploadUrl() が有効期限付きのトークンを発行し、uploadToSignedUrl() がそれを使ってアップロードします。公式の説明では、これにより「中間サーバーによる検証を必要とせず、クライアントから直接Storageへアップロードするのが容易になる」。

Next.js の Server Action で書くとこうなります。

// app/actions/upload.ts
"use server";

import { z } from "zod";
import { createClient } from "@/lib/supabase/server";

const requestSchema = z.object({
  // 拡張子の許可リスト。クライアント由来のファイル名は信用しない
  extension: z.enum(["png", "jpg", "jpeg", "webp"]),
  sizeBytes: z.number().int().positive().max(5 * 1024 * 1024),
});

export type UploadTicket =
  | { readonly ok: true; readonly path: string; readonly token: string }
  | { readonly ok: false; readonly reason: "unauthorized" | "invalid" | "failed" };

/**
 * アップロードの「許可」だけを発行する。バイト列はここを通らない。
 * ファイル名はサーバーが決める(パストラバーサルと上書き衝突の芽を断つ)。
 */
export async function createUploadTicket(input: unknown): Promise<UploadTicket> {
  const parsed = requestSchema.safeParse(input);
  if (!parsed.success) return { ok: false, reason: "invalid" };

  const supabase = await createClient();
  const { data: claims } = await supabase.auth.getClaims();
  const userId = claims?.claims.sub;
  if (!userId) return { ok: false, reason: "unauthorized" };

  // 先頭フォルダ = uid。RLS側のポリシーと対になる規約
  const path = `${userId}/${crypto.randomUUID()}.${parsed.data.extension}`;

  const { data, error } = await supabase.storage
    .from("avatars")
    .createSignedUploadUrl(path);

  if (error || !data) return { ok: false, reason: "failed" };
  return { ok: true, path: data.path, token: data.token };
}
"use client";

import { useState, useTransition } from "react";
import { createClient } from "@/lib/supabase/client";
import { createUploadTicket } from "@/app/actions/upload";

export function AvatarUploader() {
  const [pending, startTransition] = useTransition();
  const [status, setStatus] = useState<"idle" | "uploading" | "done" | "error">("idle");

  async function handleFile(file: File) {
    setStatus("uploading");
    const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
    const ticket = await createUploadTicket({ extension, sizeBytes: file.size });
    if (!ticket.ok) {
      setStatus("error");
      return;
    }

    const supabase = createClient();
    // バイト列はブラウザ → Storage の直送。サーバー関数を通らない
    const { error } = await supabase.storage
      .from("avatars")
      .uploadToSignedUrl(ticket.path, ticket.token, file);

    setStatus(error ? "error" : "done");
  }

  return (
    <div>
      <label htmlFor="avatar" className="block text-sm font-medium">
        プロフィール画像
      </label>
      <input
        id="avatar"
        type="file"
        accept="image/png,image/jpeg,image/webp"
        disabled={pending || status === "uploading"}
        // ネイティブのinputを隠さないカスタムUIにする場合もフォーカス可能に保つ
        onChange={(e) => {
          const file = e.target.files?.[0];
          if (file) startTransition(() => void handleFile(file));
        }}
      />
      {/* 状態変化をスクリーンリーダーに伝える。role="status" は暗黙で aria-live="polite" */}
      <p role="status" aria-live="polite" className="mt-2 text-sm">
        {status === "uploading" && "アップロード中です"}
        {status === "done" && "アップロードが完了しました"}
        {status === "error" && "アップロードに失敗しました。もう一度お試しください"}
      </p>
    </div>
  );
}

このコードの設計判断を明示します。

  • ファイル名はサーバーが決める。 クライアント由来のファイル名をそのままパスにすると、パストラバーサル(../)や他ユーザーのファイル上書きの経路になります。UUIDにすれば衝突もありません。
  • 拡張子は許可リストで検証する。 拡張子は嘘をつけますが、少なくとも「何を保存する契約か」をサーバーが宣言できます。実体の検証はバケットの allowedMimeTypes に任せます。
  • 先頭フォルダをuidにする。 これはRLS側の「自分のフォルダにしか書けない」ポリシーと対になる規約です。サーバーがパスを作るので、規約が破られません。
  • アップロード状態を role="status" で伝える。 進捗表示が視覚だけだと、スクリーンリーダー利用者には「何も起きていない」ように見えます。

2-4. S3互換エンドポイント

既存のS3ツールチェーンがあるなら、そのまま使えます。

import { S3Client } from '@aws-sdk/client-s3'

const client = new S3Client({
  forcePathStyle: true,
  region: 'project_region',
  endpoint: 'https://project_ref.storage.supabase.co/storage/v1/s3',
  credentials: {
    accessKeyId: 'your_access_key_id',
    secretAccessKey: 'your_secret_access_key',
  }
})

ローカル開発では http://127.0.0.1:54321/storage/v1/s3region: 'local' を使います。

注目すべきはセッショントークン方式です。accessKeyId にプロジェクトref、secretAccessKey にanonキー、sessionToken に有効なJWTアクセストークンを渡すと、公式の言葉ではこうなります。

セッショントークンで実行されるすべてのS3操作は、認証されたユーザーにスコープされる。Storageスキーマ上のRLSポリシーが尊重される。

つまり S3プロトコル経由でもRLSが効く。「S3互換にすると認可が素通しになる」という一般的な懸念が、Supabaseでは当てはまりません。既存のS3ベースの資産を持ち込みつつ、認可はRLSで一元管理できます。


3. 配信:公開URLと署名URL

公開バケット

const { data } = supabase.storage.from('bucket').getPublicUrl('filePath.jpg')
console.log(data.publicUrl)

URLの形は https://[project_id].supabase.co/storage/v1/object/public/[bucket]/[asset-name] です。

公開バケットの意味を正確に理解してください。公式の定義では「アセットのURLを持っている人は誰でもファイルにアクセスできる」。RLSは読み取りに効きません(アップロードや削除といった他の操作には効きます)。

だから判断はこうなります。

  • 公開バケットに置いてよいもの:ロゴ、OG画像、公開ドキュメント、CDNに強くキャッシュさせたい静的アセット
  • 置いてはいけないもの:請求書、契約書、個人の写真、ユーザーがアップロードした任意のファイル

「URLが推測されないから大丈夫」は成立しません。URLはリファラ、共有、ログ、スクリーンショットから漏れます。

署名URL(非公開バケット)

const { data, error } = await supabase.storage
  .from('bucket')
  .createSignedUrl('private-document.pdf', 3600) // 有効期限を秒で指定

公式が明記している重要な性質があります。

署名URLは、Authキーの変更に関わらず、有効期限まで有効であり続ける。

つまり発行してしまった署名URLは、途中で取り消せません。ユーザーの権限を剥奪しても、既に発行済みのURLは期限まで生きます。したがって設計はこうします。

  • 有効期限は用途上必要な最短にする。 画面表示のためなら数分で十分です。ダウンロードリンクをメールで送るなら数時間。「1年」は事実上の公開です。
  • 都度発行する。 署名URLをDBに保存して使い回すのは、期限管理を放棄することと同じです。

ダウンロードを強制する

ブラウザに表示させるのではなく保存させたい場合は、URLに ?download を付けます。ファイル名を指定するなら ?download=customname.jpg です。

const { data, error } = await supabase.storage
  .from('avatars')
  .download('avatar1.png', { download: 'my-custom-name.png' })

4. CDN:Smart CDN と 60秒の伝播

Supabase StorageはCDNの背後にあります。Pro以上では Smart CDN が自動的に有効です。

通常のCDNとの違いを公式はこう説明します。

Smart CDNキャッシュを有効にすると、データベース内のアセットのメタデータがエッジに同期される。これにより、アセットが変更または削除されたときにキャッシュが自動的に再検証される。

そして、

URLで異なるクエリ文字列が使われていても、変更されていないアセットへのリクエストからオリジンサーバーを守ることで、より高いキャッシュヒット率を達成する。

通常のCDNはクエリ文字列が違えば別エントリとして扱いますが、Smart CDNは同一アセットとして扱えます。分析用のクエリパラメータが付いた画像URLでも、オリジンを叩かずに済むということです。

60秒の伝播を設計に織り込む

最重要の但し書きがこれです。

ファイルが更新または削除されたとき、アセットのメタデータが世界中の全データセンターに伝播する必要があるため、CDNキャッシュの無効化には最大60秒かかることがある

「アップロードしたのに古い画像が出る」の正体はこれです。対処は2つあります。

  • 上書きしない設計にする(推奨)。 内容が変わるたびに新しいパスに書きます。ファイル名にコンテンツハッシュやバージョンを含めれば、CDNの無効化を待つ必要がそもそもありません。古いファイルは非同期に削除します。
  • 上書きが避けられないなら、60秒の遅延を仕様として受け入れる。 UIに「反映まで最大1分かかります」と明示し、キャッシュバスティング用のクエリを付けるのは最終手段(Smart CDNのヒット率を落とすため)。

cacheControl を意識して設定する

アップロード時の cacheControl オプションが、ブラウザ側のキャッシュ期間を決めます。既定は1時間で、公式は「一般的に妥当な既定値」としています。

  • 不変アセット(ハッシュ付きファイル名) → 長く設定する。ヒット率が上がり、転送量コストが直接下がります
  • 頻繁に変わるアセット → 短く。ただし第4章冒頭の通り、そもそも上書きしない設計のほうが良い

5. 画像変換:オンザフライで小さく配る

Pro以上で画像変換が使えます。オプションはこうです。

オプション
width / height1〜2500 の整数
resizecover(既定・アスペクト比を保って切り抜く)/ contain(比を保って収める)/ fill(比を保たない)
quality1〜100(既定 80)
formatorigin で元形式を維持。省略時は自動最適化

3つのメソッドで使えます。

// 公開URL
supabase.storage.from('bucket').getPublicUrl('image.jpg', {
  transform: { width: 500, height: 600 }
})

// 署名URL
supabase.storage.from('bucket').createSignedUrl('image.jpg', 60000, {
  transform: { width: 200, height: 200 }
})

// ダウンロード
supabase.storage.from('bucket').download('image.jpg', {
  transform: { width: 800, height: 300, resize: 'contain' }
})

自動フォーマット最適化が地味に強力です。公式の説明では「Storageはクライアントがサポートする最良のフォーマットを自動的に見つける」——コード変更なしにWebP等へ切り替わります。元形式を保ちたい場合だけ format: 'origin' を渡します。

制限とコストはこうです。

項目
プランPro以上
含まれる変換数月100変換(Pro / Team)
超過オリジン画像1,000枚あたり $5
画像サイズ上限25MB
解像度上限50MP
幅・高さ1〜2500 の整数

課金単位が「オリジン画像の枚数」である点が設計に効きます。同じ元画像から10種類のサイズを作っても、課金対象のオリジンは1枚です。つまりレスポンシブ画像のために複数サイズを用意することのコスト増は限定的で、むしろ配信サイズが下がるぶん転送量が減ります。

Next.js の next/image と組み合わせる場合、変換をSupabase側で行うか next/image 側で行うかは、キャッシュの所在とコストで判断します。Supabase側で変換すればCDNに乗り、Vercel側の画像最適化枠を消費しません。


6. コスト:転送量が主戦場

項目FreePro
保存容量1 GB 込み100 GB 込み、以降 $0.0213/GB
キャッシュ済み転送5 GB 込み250 GB 込み、以降 $0.03/GB

公式のスケーリングガイドが明快です。

画像が転送量の大半を占めるのが一般的。可能な限り小さく保つことで、転送量を削減しアプリケーションのパフォーマンスを向上できる。

効く順に並べるとこうなります。

  1. 画像変換で配信サイズを縮める。 2MBの写真を幅800pxのWebPで配れば、数十KBになります。これだけで転送量が1桁変わります。
  2. cacheControl を長くする。 キャッシュヒットは「ブラウザに残る=転送しない」か「CDNから返す=安い」のどちらかです。
  3. バケットのファイルサイズ上限を設定する。 巨大ファイルの流入を入口で止めます。
  4. Smart CDN のヒット率を落とさない。 キャッシュバスティングのクエリを闇雲に付けない。

リスト取得のパフォーマンス

見落とされがちな性能の穴があります。公式の指摘です。

標準の supabase.storage.list() メソッドは、フォルダ階層とオブジェクトを同時に取得するため、オブジェクト数が多いと性能が劣化する。

対処は、storage.objects に対する自前のPostgres関数を作り、必要なフィルタとページネーションだけを行うことです。フォルダ階層が不要なら、そのほうが速い。

そしてもう1つ、RLSの性能について。

Storageのテーブルに対してRLSポリシーを作るとき、対象のカラムにインデックスを追加すると参照が高速になる。

ファイル数が数万を超えたあたりから、ポリシー評価のコストが目に見えてきます(RLSの性能最適化の考え方がそのまま適用できます)。


7. 本番前チェックリスト

  • 6MBを超えるファイルにTUS再開可能アップロードを使っている(チャンクサイズは6MB固定)
  • 409 Conflict(同一パスへの同時アップロード)をUIで正しく説明している
  • アップロードのパスをサーバーが決めている(クライアントのファイル名をそのまま使わない)
  • 拡張子・MIME型・サイズを、サーバー検証とバケット制限の両方で縛っている
  • グローバルのファイルサイズ上限と、バケットごとの上限を設定した
  • 機密ファイルが公開バケットに入っていない(公開バケットは読み取りにRLSが効かない)
  • 署名URLの有効期限が用途上必要な最短である(発行後は取り消せない)
  • 署名URLをDBに保存して使い回していない
  • 上書き前提の設計を避けている(CDN無効化に最大60秒)/受け入れるならUIに明示している
  • 不変アセットに長い cacheControl を設定した
  • 画像を変換して配信している(転送量の大半は画像)
  • storage.objects に対するRLSポリシーの参照カラムにインデックスがある
  • list() の性能が問題になる規模なら、専用のPostgres関数に置き換えた
  • アップロードの状態変化がスクリーンリーダーに伝わる(role="status" / aria-live

まとめ:Storageは「置き場所」ではなく「配信経路」

Supabase Storageを本番品質にする設計は、突き詰めるとこう言えます。

ファイルは保存した瞬間ではなく、配信した瞬間にコストと事故を生む。 だから設計の重心は、アップロードよりも配信側に置きます。

  • アップロードはサーバーを細くする(許可の発行だけ。バイト列は直送)
  • 保存は上書きしない(新しいパスに書けばCDNの60秒と戦わずに済む)
  • 配信は小さくする(画像変換と cacheControl が転送量を桁で変える)
  • 公開は意図して選ぶ(公開バケットは読み取りにRLSが効かないという事実から出発する)

そしてこのすべての土台に、storage.objects へのRLSがあります。ファイルもまたテーブルの行である——この一貫したモデルが、Supabaseでファイルを扱うときの最大の強みです。データ・ファイル・リアルタイムを同じ認可の思想で守れるプラットフォームは、そう多くありません。

よくある質問

標準アップロードと再開可能アップロードはどちらを使うべきですか?
ファイルサイズで決まります。公式は「標準のファイルアップロードは6MBを超えない小さいファイルに最適」とし、「より高い信頼性のため、6MBを超えるファイルにはTUS再開可能アップロードの使用を推奨」しています。標準アップロードでも最大5GBまで送れますが、途中で切れたらやり直しになります。モバイル回線や大きな動画・PDFを扱うなら、サイズに関わらず再開可能アップロードを既定にしたほうが体験は安定します。
クライアントから直接アップロードさせても安全ですか?
storage.objects へのRLSポリシーが正しく書かれていれば安全です。むしろサーバーを経由させるほうが、関数の実行時間・メモリ・転送量を無駄に消費します。サーバーは「アップロードしてよいか」を判断して署名付きアップロードURLを発行するだけにし、バイト列はクライアントからStorageへ直接送るのが最も効率的です。ただしRLSはパスと所有者しか見ないため、ファイルサイズとMIME型はバケットの制限で別途縛ってください。
公開バケットと非公開バケットはどう使い分けますか?
公開バケットは「URLを知っている人は誰でもアクセスできる」状態です。ロゴやOG画像のように、漏れても困らず、CDNに強くキャッシュさせたいものに使います。機密ファイル——請求書、契約書、個人の写真——は非公開バケットに置き、createSignedUrl で期限付きURLを都度発行します。署名URLは発行時点で有効期限が確定し、公式によれば「Authキーの変更に関わらず有効期限まで有効」であり続けるため、期限は短く設定してください。
ファイルを上書きしたのに古い画像が表示されます。なぜですか?
CDNのキャッシュです。Smart CDNはアセットのメタデータをエッジに同期して、変更・削除時に自動的にキャッシュを無効化しますが、公式は「メタデータが世界中の全データセンターに伝播する必要があるため、CDNキャッシュの無効化には最大60秒かかることがある」としています。即時反映が要件なら、同じパスを上書きするのではなく、内容が変わるたびに新しいパス(ハッシュやバージョンを含む名前)に書く設計にしてください。
Storage のコストはどこで跳ねますか?
保存容量ではなく転送量(egress)です。Proプランは保存100GB込み・キャッシュ済み転送250GB込みで、超過は保存が$0.0213/GB、キャッシュ済み転送が$0.03/GBです。公式も「画像が転送量の大半を占めるのが一般的」と述べています。効く順に、画像変換で配信サイズを縮める、cacheControlを長く設定してブラウザとCDNのヒット率を上げる、バケットのファイルサイズ上限で巨大ファイルの流入を止める、の3つです。

参考文献

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事の実装を、案件として承ります

Supabase を土台にしたアプリを、設計から本番運用まで承ります

Realtime の設計(Broadcast / Presence / Postgres Changes の使い分けと再接続時の整合)、Auth の認証フロー・JWT署名鍵・MFA、Edge Functions での冪等な Webhook 受信、Storage のアップロード経路とコスト設計まで。認可を RLS で DB に寄せた設計を、モバイル+Web の実プロダクトで単独構築した知見で、落ちない・追える・変更しやすいアプリを実装します。

プロジェクト単位(請負)・技術顧問のどちらにも対応可能です。まずは30分の無料技術相談から。

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

あわせて読みたい