Video

Video Upload with Next.js, Supabase & Firebase: MVP Guide

Ryan Trann

December 24, 2025

7 min read

Video upload to Next.js app

Video is quickly becoming the format of choice for content on the internet. If you're building an MVP with user-generated video, you'll need to let users upload a file, store it, and play it back. This guide walks through a minimal video upload implementation using Next.js with Supabase or Firebase storage, a CDN backed URL, and a database table for tracking videos.

First time with Supabase or Firebase?

Check out the initial setup guides to get your project ready.

  • Supabase: Getting Started
  • Firebase: Web Setup

1. The MVP Video Upload Pipeline

A reliable user generated content (UGC) video flow needs five pieces:

  1. A database table to store information about uploaded videos
  2. A REST API or signed upload urls to upload videos
  3. A storage bucket to persist the uploaded videos
  4. A content delivery network (CDN) to make the video files globally available
  5. A user interface (UI) to display the file via the HTML5 <video> element

2. Store Video Metadata in Your Database

Databases store the metadata you'll need when it's time to programmatically get the right file for the right situation. Metadata such as public_url, id, user_id etc.

Let's look at two common database examples.

A. Supabase (PostgreSQL)

Create videos table

sql
create table videos (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id),
  storage_path text NOT null,
  public_url text NOT null,
  mime_type text,
  size_bytes bigint,
  created_at timestamptz default now()
);

B. Firebase (Firestore)

Firestore document creation

tsx
import { doc, setDoc } from "firebase/firestore";

await setDoc(doc(db, "videos", crypto.randomUUID()), {
  userId,
  storagePath: path,
  publicUrl: url,
  mimeType: file.type,
  sizeBytes: file.size,
  createdAt: Date.now(),
});

3. Storage Buckets + CDN for Video Hosting

Before implementing uploads, you should decide where the video files will live and how they'll be served to clients. Generally speaking, this means cloud object storage backed by a CDN.

Table stakes features (Supabase and Firebase)

Both Supabase Storage and Firebase Storage provide the core features for video uploads:

  • File storage behind a global CDN
  • Signed uploads
  • Public, cacheable URLs for playback
  • Throughput suitable for large video files

If you're hosting and serving videos, either option can work for you.

Where they differ

  • Supabase Storage (Built on Amazon S3): Uploads can be client side or driven via signed URLs generated by your backend. Which means API or server orchestrated video pipelines are potentially better suited for this option.
  • Firebase Storage: Optimized for client side usage. Its SDKs support resumable uploads and automatic retries, which makes it a strong fit for mobile apps or unreliable network conditions.

The right choice depends less on "CDN features" and more on whether your video pipeline is backend driven or client driven.

4. Implementing Next.js Video Uploads to Your Storage Bucket

The core flow is: the user selects a file, you upload it to storage, and then you write the metadata entry to your database.

Below are both Supabase and Firebase implementations, including direct uploads and signed upload URLs.

A. Supabase Upload Flow

Option 1: Direct POST to a Next.js API Route (simple, good for <100MB)

Client component

tsx
"use client";
import { useState } from "react";

export default function UploadPage() {
  const [file, setFile] = useState<File | null>(null);

  async function handleUpload() {
    if (!file) return;

    const formData = new FormData();
    formData.append("file", file);

    await fetch("/api/upload", { method: "POST", body: formData });
  }

  return (
    <div>
      <input type="file" accept="video/*" onChange={e => setFile(e.target.files?.[0] ?? null)} />
      <button onClick={handleUpload}>Upload</button>
    </div>
  );
}

API route

tsx
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@supabase/supabase-js";

export async function POST(req: NextRequest) {
  const supabase = createClient(
    process.env.SUPABASE_URL, 
    process.env.SUPABASE_KEY
  );

  const data = await req.formData();
  const file = data.get("file") as File;

  const extension = file.name.split(".").pop();
  const videoId = crypto.randomUUID();
  const path = `videos/${videoId}.${extension}`;

  const { error } = await supabase.storage
    .from("videos")
    .upload(path, file, { contentType: file.type });

  if (error) return NextResponse.json({ error: error.message }, { status: 400 });

  const url = `${process.env.SUPABASE_URL}/storage/v1/object/public/${path}`;

  const { error: dbError } = await supabase.from("videos").insert({
    id: videoId,
    user_id: "some-user-id",
    storage_path: path,
    public_url: url,
    mime_type: file.type,
    size_bytes: file.size,
  });

  if (dbError) {
    return NextResponse.json({ error: dbError.message }, { status: 500 });
  }

  return NextResponse.json({ url, path });
}

Option 2: Signed Upload URL (recommended for larger files)

API route for signed URL

tsx
export async function POST() {
  const supabase = createClient(
    process.env.SUPABASE_URL, 
    process.env.SUPABASE_KEY
  );

  const videoId = crypto.randomUUID();
  const path = `videos/${videoId}.mp4`;
  const { data, error } = await supabase.storage.from("videos").createSignedUploadUrl(path);

  if (error) return NextResponse.json({ error: error.message }, { status: 400 });

  return NextResponse.json({ uploadUrl: data.signedUrl, path, videoId });
}

Client upload with signed URL

tsx
await fetch(uploadUrl, { method: "PUT", body: file });

Insert metadata after upload

tsx
await supabase.from("videos").insert({
  id: videoId,
  user_id,
  storage_path: path,
  public_url: `${process.env.SUPABASE_URL}/storage/v1/object/public/${path}`,
  mime_type: file.type,
  size_bytes: file.size,
});

B. Firebase Upload Flow

Firebase handles everything client-side.

Client upload (Firebase Storage)

tsx
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";

const storage = getStorage();
const videoId = crypto.randomUUID()
const path = `videos/${videoId}`;
const storageRef = ref(storage, path);

const task = uploadBytesResumable(storageRef, file);

task.on("state_changed", null, console.error, async () => {
  const url = await getDownloadURL(task.snapshot.ref);

  await setDoc(doc(db, "videos", crypto.randomUUID()), {
    id: videoId,
    userId,
    storagePath: path,
    publicUrl: url,
    mimeType: file.type,
    sizeBytes: file.size,
    createdAt: Date.now(),
  });
});

5. Displaying Video in Your App

Once you have a video URL:

Basic video element

html
<video
  src={url}
  controls
  playsInline
/>

For looping autoplay:

Autoplay video

html
<video
  src={url}
  controls
  autoplay
  muted
  loop
  playsInline
/>

6. Extra Steps You Shouldn't Skip

Limit file size before uploading

File size validation

tsx
if (file.size > 200 * 1024 * 1024) {
  alert("Max size is 200MB");
  return;
}

Validate MIME type before uploading

MIME type validation

tsx
if (!file.type.startsWith("video/")) {
  alert("File must be a video");
  return;
}

Generate a thumbnail (optional)

This step requires ffmpeg, which can be a tricky thing to get setup properly in a Node.js environment. I recommend you skip this early on and add it later when you've got time to invest. Thumbnail images or posters can be really useful for UX purposes to give the user the perception of the UI being ready, while the video is buffering.

7. Full Architecture Summary

Client → API or Signed URL → Storage Bucket → CDN → DB record → <video> playback

That's the MVP video pipeline for adding user-generated video to a Next.js application without dealing with transcoding, queues, workers and distributed architecture.

This system won't last much longer than an MVP—if you need something reliable and scalable but you don't want to build it yourself, give Hyperserve a try. We've done all the hard work of building out Video Architecture at Scale and wrapped it in a simple API that makes adding video nearly as easy as adding images.

Want to add video to your app quickly?
Try Hyperserve!

Hyperserve

Video Hosting API made simple.

Product

FeaturesPricingBlog

© 2025 Hyperserve. All rights reserved.