useDownload
Headless React hook for presigned S3 downloads with browser navigation, fetch streams, and inline preview.
useDownload requests signed GET URLs from your server and handles browser-native downloads or client-side fetch streaming with live progress tracking.
"use client";
import { useDownload } from "@dimah-s3/react";
export function DownloadAvatar({ avatarKey }: { avatarKey: string }) {
const { download, isPending } = useDownload({
route: "avatar",
});
return (
<button
type="button"
onClick={() => void download(avatarKey)}
disabled={isPending}
className="btn"
>
{isPending ? "Generating link…" : "Download Avatar"}
</button>
);
}Download modes
Browser navigation (mode: "navigate", default)
Requests a presigned GET URL and navigates the browser directly to S3. S3 immediately responds with the binary file stream and triggers the browser's native file save dialog.
const { download, presign, phase, isPending } = useDownload({
route: "avatar",
onInitiated: (key) => {
console.log("Browser handed download URL for:", key);
},
});
// Trigger download
await download(avatarKey);
// Or get the presigned URL directly without triggering browser navigation
const { url, expiresIn } = await presign(avatarKey);Fetch streaming (mode: "fetch")
Fetches the object bytes through a client-side fetch stream. Provides fine-grained byte progress, speed calculation, and abortable cancellation.
const { download, cancel, progress, isDownloading, isPending } = useDownload({
route: "document",
mode: "fetch",
onProgress: (key, progress) => {
console.log(
`Downloaded ${progress.percent}% (${progress.loaded}/${progress.total})`,
);
},
onSuccess: (key, fileName) => {
console.log(`Saved ${fileName} to disk`);
},
});
return (
<div>
<button onClick={() => void download(documentKey)} disabled={isPending}>
{isDownloading ? `Downloading: ${progress.percent}%` : "Download File"}
</button>
{isDownloading && (
<button type="button" onClick={cancel}>
Cancel
</button>
)}
</div>
);Inline preview with useObjectUrl
For displaying private S3 files inline (e.g. <img>, <video>, <audio>, or <iframe>), use useObjectUrl. It requests a signed URL with disposition: "inline" and automatically caches the signed URL in memory until shortly before its expiration window.
"use client";
import { useObjectUrl } from "@dimah-s3/react";
export function AvatarImage({ avatarKey }: { avatarKey: string }) {
const { url, isLoading, refresh } = useObjectUrl({
route: "avatar",
objectKey: avatarKey,
disposition: "inline",
});
if (isLoading)
return <div className="size-16 rounded-full bg-muted animate-pulse" />;
if (!url) return null;
return (
<img
src={url}
alt="User Avatar"
className="size-16 rounded-full object-cover"
/>
);
}Type reference
import type {
UseNavigateDownloadOptions,
UseNavigateDownloadReturn,
UseFetchDownloadOptions,
UseFetchDownloadReturn,
UseObjectUrlOptions,
UseObjectUrlReturn,
} from "@dimah-s3/react";UseNavigateDownloadOptions
UseFetchDownloadOptions
Prop
Type
UseNavigateDownloadReturn
UseFetchDownloadReturn
Prop
Type
UseObjectUrlOptions
Prop
Type
UseObjectUrlReturn
Prop
Type
Frequently asked questions
Pass the filename as the second argument to download or presign:
await download(avatarKey, "custom-avatar-name.png");Yes. useDownload tracks objectKey internally so loading states, errors, and progress indicators stay scoped to the button matching the active key.
useObjectUrl caches signed URLs in memory indexed by route, key, disposition, and filename. The cached URL is returned instantly on subsequent renders until 15 seconds before its expiresIn TTL. Calling refresh() invalidates the cache and requests a fresh URL.