Examples & Demos
Experience pushduck with interactive demos and real-world examples. All demos use live Cloudflare R2 integration.
Live Demos: These are fully functional demos using real Cloudflare R2 storage. Files are uploaded to a demo bucket and may be automatically cleaned up. Don't upload sensitive information.
Having Issues? If uploads aren't working (especially with next dev --turbo
), check our Troubleshooting Guide for common solutions including the known Turbo mode compatibility issue.
Interactive Upload Demo
The full-featured demo showcasing all capabilities:
🚀 Full-Featured Demo
Complete upload experience with type-safe client, progress tracking, and error handling
Live Demo: Files are uploaded to Cloudflare R2. Don't upload sensitive information.
Drag & drop files or click to browse
Maximum 5 files allowed
Drop files above or click to browse
💻 Code powering this demo:
// Enhanced type-safe client
const imageUpload = upload.imageUpload();
// Upload with full type safety
await imageUpload.uploadFiles(files);
// Access state with TypeScript inference
const {
files, // Individual file progress
isUploading, // Upload status
errors, // Any errors
progress, // Overall progress (0-100)
uploadSpeed, // Overall bytes/sec
eta // Overall time remaining
} = imageUpload;
ETA & Speed Tracking: Upload speed (MB/s) and estimated time remaining (ETA) appear below the progress bar during active uploads. Try uploading larger files (1MB+) to see these metrics in action! ETA becomes more accurate after the first few seconds of upload.
Image-Only Upload
Focused demo for image uploads with preview capabilities:
🖼️ Image Upload Demo
Optimized for images with instant previews and validation
Drag & drop files or click to browse
Maximum 5 files allowed
Drop files above or click to browse
Document Upload
Streamlined demo for document uploads:
📄 Document Upload Demo
Professional document handling with type validation
Drag & drop files or click to browse
Maximum 3 files allowed
Drop files above or click to browse
Key Features Demonstrated
✅ Type-Safe Client
// Property-based access with full TypeScript inference
const imageUpload = upload.imageUpload();
const fileUpload = upload.fileUpload();
// No string literals, no typos, full autocomplete
await imageUpload.uploadFiles(selectedFiles);
⚡ Real-Time Progress
- Individual file progress tracking with percentage completion
- Upload speed monitoring (MB/s) with live updates
- ETA calculations showing estimated time remaining
- Pause/resume functionality (coming soon)
- Comprehensive error handling with retry mechanisms
🔒 Built-in Validation
- File type validation (MIME types)
- File size limits with user-friendly errors
- Custom validation middleware
- Malicious file detection
🌐 Provider Agnostic
- Same code works with any S3-compatible provider
- Switch between Cloudflare R2, AWS S3, DigitalOcean Spaces
- Zero vendor lock-in
Code Examples
"use client";
import { upload } from "@/lib/upload-client";
export function SimpleUpload() {
const { uploadFiles, files, isUploading } = upload.imageUpload();
return (
<div>
<input
type="file"
multiple
accept="image/*"
onChange={(e) => uploadFiles(Array.from(e.target.files || []))}
disabled={isUploading}
/>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
<span>{file.status}</span>
{file.url && <a href={file.url}>View</a>}
</div>
))}
</div>
);
}
// app/api/upload/route.ts
import { createUploadConfig } from "pushduck/server";
const { s3, } = createUploadConfig()
.provider("cloudflareR2",{
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
bucket: process.env.R2_BUCKET!,
})
.defaults({
maxFileSize: "10MB",
acl: "public-read",
})
.build();
const uploadRouter = s3.createRouter({
imageUpload: s3
.image()
.max("5MB")
.formats(["jpeg", "png", "webp"])
.middleware(async ({ file, metadata }) => {
// Custom authentication and metadata
const session = await getServerSession();
if (!session) throw new Error("Unauthorized");
return {
...metadata,
userId: session.user.id,
uploadedAt: new Date().toISOString(),
};
})
.onUploadComplete(async ({ file, url, metadata }) => {
// Post-upload processing
console.log(`Upload complete: ${url}`);
await saveToDatabase({ url, metadata });
}),
});
export const { GET, POST } = uploadRouter.handlers;
export type AppRouter = typeof uploadRouter;
"use client";
import { upload } from "@/lib/upload-client";
export function RobustUpload() {
const { uploadFiles, files, errors, reset } = upload.imageUpload();
const handleUpload = async (fileList: FileList) => {
try {
await uploadFiles(Array.from(fileList));
} catch (error) {
console.error("Upload failed:", error);
// Error is automatically added to the errors array
}
};
return (
<div>
<input
type="file"
onChange={(e) => e.target.files && handleUpload(e.target.files)}
/>
{/* Display errors */}
{errors.length > 0 && (
<div className="error-container">
<h4>Upload Errors:</h4>
{errors.map((error, index) => (
<p key={index} className="error">{error}</p>
))}
<button onClick={reset}>Clear Errors</button>
</div>
)}
{/* Display files with status */}
{files.map(file => (
<div key={file.id} className={`file-item ${file.status}`}>
<span>{file.name}</span>
<span>{file.status}</span>
{file.status === "uploading" && (
<progress value={file.progress} max={100} />
)}
{file.status === "error" && (
<span className="error">{file.error}</span>
)}
{file.status === "success" && file.url && (
<a href={file.url} target="_blank">View File</a>
)}
</div>
))}
</div>
);
}
Real-World Use Cases
Profile Picture Upload
Single image upload with instant preview and crop functionality.
Document Management
Multi-file document upload with categorization and metadata.
Media Gallery
Batch image upload with automatic optimization and thumbnail generation.
File Sharing
Secure file upload with expiration dates and access controls.