API

Next.js: Remove Image Backgrounds with a Route Handler and a React Upload Component

September 6, 20266 min readBy BG Clear Editorial

In Next.js the temptation is to call the background-removal API from the client component, and the reason not to is the same as everywhere: the key would ship to the browser. A route handler fixes that in about 30 lines, and the App Router's Request/Response model makes forwarding multipart trivial. Below: the route handler, a client component with instant free preview and paid HD export, and the limits you hit on Vercel and similar hosts.

In this guide

The route handler

// app/api/cutout/route.ts
import { NextRequest } from "next/server";

export const runtime = "nodejs";          // Buffer + 25 MB bodies; edge runtime limits body size
export const maxDuration = 60;            // seconds — sync removals finish well under this

export async function POST(req: NextRequest) {
  const inbound = await req.formData();
  const file = inbound.get("image");
  if (!(file instanceof File)) return Response.json({ error: "image missing" }, { status: 400 });
  if (file.size > 25 * 1024 * 1024) return Response.json({ error: "max 25 MB" }, { status: 413 });

  const size = req.nextUrl.searchParams.get("size") === "full" ? "full" : "preview"; // preview is free
  const form = new FormData();
  form.append("image_file", file, file.name);
  form.append("size", size);
  form.append("format", "png");

  const upstream = await fetch("https://www.bgclear.ai/api/v1/remove", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BGCLEAR_API_KEY}`,
      "Idempotency-Key": `${size}-${file.size}-${file.name}`,   // safe retries, never double-charged
    },
    body: form,
  });

  if (!upstream.ok) {
    return Response.json(await upstream.json(), { status: upstream.status });
  }
  return new Response(upstream.body, {
    headers: {
      "Content-Type": upstream.headers.get("content-type") ?? "image/png",
      "X-Credits-Remaining": upstream.headers.get("x-credits-remaining") ?? "",
      "Cache-Control": "no-store",
    },
  });
}

Put BGCLEAR_API_KEY in .env.local (no NEXT_PUBLIC_ prefix — that prefix is exactly what would leak it). Add your auth check at the top of the handler before forwarding; without one, anyone who finds the route spends your credits.

The client component

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

export default function Cutout() {
  const [file, setFile] = useState<File | null>(null);
  const [preview, setPreview] = useState<string | null>(null);
  const [busy, setBusy] = useState<"preview" | "full" | null>(null);
  const [error, setError] = useState("");

  async function run(size: "preview" | "full") {
    if (!file) return;
    setBusy(size); setError("");
    const form = new FormData();
    form.append("image", file);
    const res = await fetch(`/api/cutout?size=${size}`, { method: "POST", body: form });
    setBusy(null);
    if (!res.ok) {
      const { error } = await res.json().catch(() => ({ error: { message: res.statusText } }));
      setError(error?.message ?? "Failed");
      return;
    }
    const url = URL.createObjectURL(await res.blob());
    if (size === "preview") setPreview(url);
    else Object.assign(document.createElement("a"), { href: url, download: "cutout.png" }).click();
  }

  return (
    <div>
      <input type="file" accept="image/*" onChange={(e) => { setFile(e.target.files?.[0] ?? null); setPreview(null); }} />
      <button disabled={!file || !!busy} onClick={() => run("preview")}>
        {busy === "preview" ? "Removing…" : "Preview (free)"}
      </button>
      {preview && (
        <>
          <img src={preview} alt="cutout preview" style={{ maxWidth: 400, background: "repeating-conic-gradient(#eee 0 25%, #fff 0 50%) 0 0/16px 16px" }} />
          <button disabled={!!busy} onClick={() => run("full")}>{busy === "full" ? "Exporting…" : "Download HD"}</button>
        </>
      )}
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

The checkerboard background makes transparency visible. Preview first, HD on demand: previews are free, and only the HD export costs a credit — the user sees the result before anyone pays.

Production details

Body size: Vercel serverless functions accept request bodies up to 4.5 MB; for larger uploads, upload directly to blob storage (Vercel Blob, S3 presigned URL) and send image_url to the API from the route handler instead of forwarding bytes — that is a JSON call with Accept: application/json, and the result URL it returns can be stored straight into your database. Streaming: returning upstream.body streams the PNG without buffering. Rate limit: 60 requests a minute per key; queue bursts in a job runner rather than hammering from many concurrent users. Large originals: synchronous full-size requests are capped at 4 MP; route bigger ones to /api/v1/jobs and poll from a server action (bulk guide). Cost: one credit per HD export — $9 for 100, $39 for 500, $129 for 2,000 — with no subscription (pricing). Coming from remove.bg? Same fields, different URL and header: migration page.

Frequently asked questions

Can I use a Server Action instead of a route handler?

Yes for the upload, but a route handler is better for returning binary: server actions serialise return values, so you would have to base64 the PNG.

Does this work on the Edge runtime?

The forwarding works, but edge functions have small body limits and no Buffer; use runtime = "nodejs" as shown, or upload to blob storage and send image_url.

How do I show a progress bar?

Use XMLHttpRequest or a fetch with a ReadableStream body for upload progress; the removal itself takes 2–5 seconds, so a spinner after upload is usually enough.

How much does it cost?

Previews are free. Full-resolution exports are one credit each, from $0.065 to $0.09 depending on the pack; credits never expire.

Ship it with the bgclear API

remove.bg-compatible endpoints, credits from $9 for 100 images, no subscription. Pay, get a key, make your first call in under two minutes.

Get API credits →

Tools for this guide

Background Removal API Free

background removal api free

Cheapest Background Remover API

cheapest background remover api

Image Segmentation API

image segmentation api

Keep reading

API

Remove Image Backgrounds in Node.js with fetch and FormData (No SDK)

Node 18+ built-in fetch, a file upload, a URL input, an Express proxy endpoint so your browser never sees the key, and retries that cannot double-charge.

API

Background Removal in Flutter and React Native Apps: the Safe API Pattern

Why the API key must never ship in the app, a 30-line proxy endpoint, then the Flutter (http + MultipartRequest) and React Native (fetch + FormData) client code with progress and error handling.

API

Bulk Background Removal via API: Async Jobs, Batches of 50 and Webhooks

How to process a catalogue of thousands of images without babysitting a script: batch submission, callbacks instead of polling, credit accounting, and what to do when the queue is full.

API

7 remove.bg API Alternatives Compared (2026): Price per Image, Free Tier, Drop-in Compatibility

With remove.bg closing on 1 Dec 2026, here is what the alternatives actually charge per image, what you get free, and which ones let you keep your existing request code.