Grabrgrabr
Back to home

Using grabr with Next.js

Because grabr writes directly to the filesystem and performs high-speed parallel network requests, it is designed to run in server environments (Node.js or Bun). You cannot run grabr directly inside a browser component.

In this tutorial, we will integrate grabr into a Next.js App Router project using a Server Action to trigger a backend download when a user clicks a button.

1. Create a Next.js Project

Start by initializing a fresh Next.js app and installing grabr.

bash
bun create next-app my-grabr-app
cd my-grabr-app
bun add @linuxctrl/grabr

2. Build the Server Action

We need a secure backend environment to run grabr. We will create a Next.js Server Action that instantiates the downloader, adds a job, and starts the transfer.

Create a file at src/app/actions.ts:

typescript
"use server";


import { Downloader } from "@linuxctrl/grabr";
import path from "path";
import fs from "fs";


export async function triggerDownload(url: string, filename: string) {
  const outputDir = path.join(process.cwd(), "downloads");
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }


  const downloader = new Downloader();
  await downloader.start();


  try {
    const job = await downloader.addJob(url, {
      outputDir,
      filename,
      chunks: 8,
    });


    // Track progress via EventEmitter
    downloader.on("job:progress", ({ jobId, speed, eta, downloadedBytes, totalBytes }) => {
      console.log(`${jobId}: ${Math.round(downloadedBytes / totalBytes * 100)}% at ${Math.round(speed / 1e6)} MB/s, ETA: ${eta}s`);
    });


    downloader.on("job:status", ({ jobId, status }) => {
      console.log(`${jobId}: ${status}`);
    });


    console.log(`Started downloading ${job.filename} (ID: ${job.id})`);
    return { success: true, jobId: job.id };
  } catch (error) {
    console.error("Download failed to start:", error);
    return { success: false, error: String(error) };
  }
}

3. Create the Frontend UI

Now we will build a simple client component where the user can enter a URL and click a button to tell the server to fetch it using grabr.

Open src/app/page.tsx and replace its contents:

typescript
"use client";


import { useState } from "react";
import { triggerDownload } from "./actions";


export default function HomePage() {
  const [url, setUrl] = useState("https://speed.hetzner.de/100MB.bin");
  const [status, setStatus] = useState("");


  const handleDownload = async () => {
    setStatus("Starting grabr engine...");
    const result = await triggerDownload(url, "test-file.bin");


    if (result.success) {
      setStatus("Downloading! Check your terminal and the ./downloads folder.");
    } else {
      setStatus(`Error: ${result.error}`);
    }
  };


  return (
    <main className="p-10 max-w-xl mx-auto space-y-4">
      <h1 className="text-2xl font-bold">Grabr Next.js Demo</h1>
      <input
        type="text"
        value={url}
        onChange={(e) => setUrl(e.target.value)}
        className="w-full p-2 border rounded text-black"
        placeholder="Enter file URL..."
      />
      <button
        onClick={handleDownload}
        className="px-4 py-2 bg-cyan-500 text-white font-bold rounded"
      >
        Download on Server
      </button>
      {status && <p className="text-sm font-medium mt-4">{status}</p>}
    </main>
  );
}

4. Run the Application

bash
bun run dev

Open your browser, click Download on Server, and watch your server terminal! grabr will instantly parallel-chunk the file directly to your Next.js server's ./downloads folder at maximum speed.

Pro Tip: In production, run grabr daemon start as a background service and connect to its built-in WebSocket at /ws for real-time progress — no extra server or socket library needed. The daemon also exposes a REST API at /api/jobs for queue management.