npm.io
0.1.0 • Published yesterday

next-media-uploader

Licence
MIT
Version
0.1.0
Deps
2
Size
247 kB
Vulns
0
Weekly
0

next-media-uploader

Media uploads for Next.js — switch between Backblaze B2 and Supabase Storage with one API.

Install

npm install next-media-uploader

For Supabase uploads, also install peer dependencies:

npm install @supabase/supabase-js tus-js-client

Choose a provider

Set the default provider in .env.local:

NEXT_PUBLIC_UPLOAD_PROVIDER=b2        # or "supabase"

Or pass provider per component:

useUpload({ provider: "b2" })
useUpload({ provider: "supabase" })
Provider How it uploads Best for
b2 Server-side via API route Private buckets, B2 credentials stay on server
supabase Client-side TUS resumable upload Large files, bypasses API body limits

Environment variables

Backblaze B2 (server-only)
B2_KEY_ID=your_key_id
B2_APPLICATION_KEY=your_application_key
B2_BUCKET_NAME=your_bucket_name
B2_REGION=us-east-005
B2_BUCKET_PUBLIC=false
Supabase (browser-safe)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_SUPABASE_IMAGE_BUCKET=images
NEXT_PUBLIC_SUPABASE_VIDEO_BUCKET=videos

Set both bucket vars to the same name if using one bucket for everything.


Quick start

Backblaze B2

1. API route

// app/api/upload/route.ts
import { createUploadRoute } from "next-media-uploader/next";

export const POST = createUploadRoute();

2. React hook

"use client";
import { useUpload } from "next-media-uploader/react";

const { upload, isUploading, url } = useUpload({
  provider: "b2",
  accept: "both",
  maxSize: 50 * 1024 * 1024,
});
Supabase

No API route needed — uploads go directly to Supabase via TUS.

"use client";
import { useUpload } from "next-media-uploader/react";

const { upload, progress } = useUpload({
  provider: "supabase",
  accept: "image",
  maxSize: 5 * 1024 * 1024,
  bucket: "images", // optional — auto-picked from accept
});

Custom UI

Three levels of customization:

1. Default design (zero config)
<Uploader provider="b2" accept="image" maxSize={5 * 1024 * 1024} />
2. Slots — override individual parts
<Uploader
  provider="b2"
  accept="image"
  slots={{
    trigger: ({ openFilePicker, isUploading }) => (
      <button onClick={openFilePicker} disabled={isUploading}>
        Pick a photo
      </button>
    ),
    preview: ({ result, reset }) => (
      <div>
        <img src={result?.url} alt="" />
        <button onClick={reset}>Remove</button>
      </div>
    ),
  }}
/>

Available slots: trigger, progress, error, preview

3. Children — full control
import { Uploader, UploaderInput } from "next-media-uploader/react";

<Uploader provider="supabase" accept="video" maxSize={100 * 1024 * 1024}>
  {({ openFilePicker, isUploading, progress, error, result, inputRef, getInputProps }) => (
    <div className="your-custom-design">
      <button onClick={openFilePicker} disabled={isUploading}>
        {isUploading ? `Uploading ${Math.round(progress * 100)}%` : "Pick video"}
      </button>
      <UploaderInput inputRef={inputRef} getInputProps={getInputProps} />
      {error && <p>{error}</p>}
      {result && <video src={result.url} controls />}
    </div>
  )}
</Uploader>

API reference

Export From Description
useUpload(options) next-media-uploader/react Hook — provider, accept, maxSize, custom UI
Uploader next-media-uploader/react Render-props upload component
createUploadRoute() next-media-uploader/next B2 API route factory
createB2Client() next-media-uploader/server B2 S3 client
uploadFile() next-media-uploader/server Server-side B2 upload
uploadToSupabase() next-media-uploader/supabase Direct TUS upload
deleteFromSupabase() next-media-uploader/supabase Delete by URL
useUpload / Uploader options
Option Type Default Description
provider "b2" | "supabase" env or "b2" Storage provider
accept "image" | "video" | "both" "both" Allowed file types
maxSize number Max bytes
bucket "images" | "videos" auto Supabase bucket (images/videos)
uploadUrl string "/api/upload" B2 API endpoint
onProgress (0–1) => void Upload progress

Supabase setup

  1. Create bucket(s) in Supabase Storage (public for getPublicUrl previews)
  2. Add storage policies for anon INSERT and DELETE
  3. TUS endpoint is used automatically: {SUPABASE_URL}/storage/v1/upload/resumable

See SUPABASE_MEDIA_UPLOAD.md for full setup details.

License

MIT