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.
bun create next-app my-grabr-app
cd my-grabr-app
bun add @linuxctrl/grabr2. 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:
"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:
"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
bun run devOpen 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.
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.