vercelsupabaseintegrationtutorialbucket-doc

Add Video Processing to Your Vercel + Supabase App in 10 Minutes

·Javid Jamae·7 min read
Add Video Processing to Your Vercel + Supabase App in 10 Minutes

Most Vercel + Supabase apps hit the same wall: a user uploads a video, and you need to do something with it. Compress it, convert it, extract a thumbnail. You Google "ffmpeg vercel" and realize serverless functions time out after 60 seconds, and video processing takes way longer than that.

You don't need to spin up a separate server. FFmpeg Micro is a cloud API that processes video with a single HTTP call. It fits into the Vercel + Supabase stack without any infrastructure changes.

This guide walks through the full flow: upload to Supabase Storage, process with FFmpeg Micro, and store the result back in Supabase. You can have it running in under 10 minutes.

Why you can't just run FFmpeg in a Vercel function

Vercel Serverless Functions have a 60-second timeout on the Pro plan (10 seconds on Hobby). A 30-second video transcode can take 2-3 minutes depending on the codec and resolution. Even if you could fit it in the timeout, the function would burn compute the entire time.

FFmpeg Micro handles the processing asynchronously. You send a request, get a job ID, and poll or use a webhook to know when it's done. Your Vercel function stays fast because it's only making HTTP calls, not running FFmpeg binaries.

The architecture (3 pieces)

Your app needs three things:

  • Supabase Storage for file uploads and final output storage
  • FFmpeg Micro API for the actual video processing
  • A Vercel API route (or Edge Function) that ties them together

The flow looks like this: user uploads a video to Supabase Storage, your API route sends the public URL to FFmpeg Micro, FFmpeg Micro processes it and returns a download URL, and your app saves the result back to Supabase.

Step 1: Get your FFmpeg Micro API key

Sign up at ffmpeg-micro.com and grab your API key from the dashboard. The free tier gives you enough minutes to test everything in this guide.

Step 2: Set up the environment variables

Add these to your Vercel project (or .env.local for local dev):

FFMPEG_MICRO_API_KEY=your_api_key_here
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

Step 3: Create the transcode API route

This is a Next.js API route that accepts a Supabase Storage URL and sends it to FFmpeg Micro for processing.

// app/api/transcode/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const { videoUrl, outputFormat } = await req.json();

  const response = await fetch("https://api.ffmpeg-micro.com/v1/transcodes", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.FFMPEG_MICRO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      inputs: [{ url: videoUrl }],
      outputFormat: outputFormat || "mp4",
      preset: { quality: "medium", resolution: "720p" },
    }),
  });

  const job = await response.json();
  return NextResponse.json({ jobId: job.id, status: job.status });
}

The inputs array takes objects with a url field. You can pass a public Supabase Storage URL directly. The preset field sets quality and resolution without needing to know FFmpeg flags.

Step 4: Poll for completion

FFmpeg Micro processes video asynchronously. You need to check the job status until it's completed.

// lib/ffmpeg-micro.ts
export async function pollTranscode(jobId: string): Promise<string> {
  const maxAttempts = 30;

  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetch(
      `https://api.ffmpeg-micro.com/v1/transcodes/${jobId}`,
      {
        headers: {
          Authorization: `Bearer ${process.env.FFMPEG_MICRO_API_KEY}`,
        },
      }
    );

    const job = await res.json();

    if (job.status === "completed") {
      const dlRes = await fetch(
        `https://api.ffmpeg-micro.com/v1/transcodes/${jobId}/download`,
        {
          headers: {
            Authorization: `Bearer ${process.env.FFMPEG_MICRO_API_KEY}`,
          },
        }
      );
      const { url } = await dlRes.json();
      return url;
    }

    if (job.status === "failed") {
      throw new Error(`Transcode failed: ${job.error_message}`);
    }

    await new Promise((r) => setTimeout(r, 2000));
  }

  throw new Error("Transcode timed out");
}

Step 5: Save the result back to Supabase

Once you have the download URL from FFmpeg Micro, fetch the processed video and upload it to your Supabase Storage bucket.

// lib/save-to-supabase.ts
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

export async function saveProcessedVideo(
  downloadUrl: string,
  outputPath: string
) {
  const videoResponse = await fetch(downloadUrl);
  const videoBuffer = await videoResponse.arrayBuffer();

  const { data, error } = await supabase.storage
    .from("videos")
    .upload(outputPath, videoBuffer, {
      contentType: "video/mp4",
      upsert: true,
    });

  if (error) throw error;

  const {
    data: { publicUrl },
  } = supabase.storage.from("videos").getPublicUrl(outputPath);

  return publicUrl;
}

Putting it together

The full flow in one API route:

// app/api/process-video/route.ts
import { NextRequest, NextResponse } from "next/server";
import { pollTranscode } from "@/lib/ffmpeg-micro";
import { saveProcessedVideo } from "@/lib/save-to-supabase";

export async function POST(req: NextRequest) {
  const { videoUrl, outputFormat } = await req.json();

  // 1. Start the transcode
  const createRes = await fetch(
    "https://api.ffmpeg-micro.com/v1/transcodes",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.FFMPEG_MICRO_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        inputs: [{ url: videoUrl }],
        outputFormat: outputFormat || "mp4",
        preset: { quality: "medium", resolution: "720p" },
      }),
    }
  );

  const job = await createRes.json();

  // 2. Wait for processing to finish
  const downloadUrl = await pollTranscode(job.id);

  // 3. Save to Supabase Storage
  const outputPath = `processed/${Date.now()}-output.${outputFormat || "mp4"}`;
  const publicUrl = await saveProcessedVideo(downloadUrl, outputPath);

  return NextResponse.json({ publicUrl, jobId: job.id });
}

Advanced: use raw FFmpeg options

The preset mode covers common cases. But if you need something specific, like extracting audio or adding a watermark, you can pass raw FFmpeg options instead:

{
  "inputs": [{ "url": "https://your-supabase-url.co/storage/v1/object/public/videos/input.mp4" }],
  "outputFormat": "mp4",
  "options": [
    { "option": "-c:v", "argument": "libx264" },
    { "option": "-crf", "argument": "23" },
    { "option": "-c:a", "argument": "aac" }
  ]
}

This gives you the full flexibility of FFmpeg without having to install or manage it.

Common pitfalls

Supabase Storage URLs must be public (or signed). FFmpeg Micro needs to download your video. Either make the bucket public or generate a signed URL with a long enough expiry (the processing can take a few minutes).

Don't poll from the client. Run the poll loop server-side in your API route or use a background job. Client-side polling ties up a browser tab and exposes your API key.

Watch your function timeout. The polling loop above can run for up to 60 seconds. On Vercel Pro, that's fine for Serverless Functions. On the Hobby plan, consider using a webhook callback instead of polling.

**Use outputFormat not format.** The FFmpeg Micro API uses outputFormat in the request body. If you pass format, it'll be silently ignored and you'll get unexpected defaults.

FAQ

Can I use this with the Vercel Hobby plan?
Yes, but the 10-second function timeout means you can't poll inline. Use a webhook callback or process the video client-side by calling FFmpeg Micro directly from the browser (your API key should stay server-side, so you'd need a thin proxy route).

Does FFmpeg Micro support webhooks?
Not yet as a built-in feature, but you can build your own callback by storing the job ID in your database and running a cron job or background function to check status.

What video formats does FFmpeg Micro accept?
MP4, WebM, MOV, AVI, and MKV for input. Output formats are MP4, WebM, and MOV. Most Supabase apps deal with MP4, which works perfectly.

How much does it cost?
FFmpeg Micro bills per minute of video processed. The free tier includes enough for testing. For production, pricing starts at $19/month. Check ffmpeg-micro.com/pricing for current rates.

Can I process multiple videos in parallel?
Yes. Each API call is independent. Fire off multiple transcode requests and poll them separately. FFmpeg Micro handles the scaling.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free