Quick Start

Get production-ready file uploads working in your Next.js app in under 2 minutes.

Quick Start

Get production-ready file uploads working in your Next.js app in under 2 minutes with our CLI tool. Interactive setup, just one command.

🚀 New! Use our CLI for instant setup: npx @pushduck/cli@latest init - handles everything automatically!

Choose Your Setup Method

⚡ Interactive CLI Setup

Get everything set up instantly with our interactive CLI:

npx @pushduck/cli@latest init

That's it! The CLI will:

  • ✅ Install dependencies automatically
  • ✅ Set up your chosen provider (AWS S3, Cloudflare R2, etc.)
  • ✅ Create API routes with type safety
  • ✅ Generate example components
  • ✅ Configure environment variables
  • ✅ Create and configure your S3 bucket

What you get:

  • Production-ready upload API in app/api/upload/route.ts
  • Type-safe upload client in lib/upload-client.ts
  • Example components in components/ui/
  • Working demo page in app/upload/page.tsx

📚 Full CLI Documentation →

Example CLI Output:

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   🚀 Welcome to Pushduck                                    │
│                                                             │
│   Let's get your file uploads working in 2 minutes!         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

🔍 Detecting your project...
  ✓ Next.js App Router detected
  ✓ TypeScript configuration found

? Which cloud storage provider would you like to use?
❯ AWS S3 (recommended)
  Cloudflare R2 (S3-compatible, global edge)
  DigitalOcean Spaces (simple, affordable)

✨ Generated files:
  ├── app/api/upload/route.ts
  ├── app/upload/page.tsx
  ├── components/ui/upload-button.tsx
  ├── lib/upload-client.ts
  └── .env.example

🎉 Setup complete! Your uploads are ready.

🔧 Manual Setup

If you prefer to set things up manually or need custom configuration:

Prerequisites

  • Next.js 13+ with App Router
  • An S3-compatible storage provider (we'll use AWS S3 in this guide)
  • Node.js 18+

Install Pushduck

npm install pushduck

Using a different package manager?

npm install pushduck
pnpm add pushduck
yarn add pushduck
bun add pushduck

Set Environment Variables

Create a .env.local file in your project root with your S3 credentials:

# .env.local
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-east-1
AWS_S3_BUCKET_NAME=your-bucket-name

Don't have S3 credentials yet? Follow our AWS S3 setup guide to create a bucket and get your credentials in 2 minutes.

Create Your Upload Router

Create an API route to handle file uploads:

// app/api/s3-upload/route.ts
import { createUploadConfig } from "pushduck/server";

const { s3 } = createUploadConfig()
  .provider("cloudflareR2",{
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    accessKeyId: process.env.CLOUDFLARE_ACCESS_KEY_ID!,
    secretAccessKey: process.env.CLOUDFLARE_SECRET_ACCESS_KEY!,
    bucket: process.env.CLOUDFLARE_BUCKET_NAME!,
    region: "auto",
  })
  .build();

const router = s3.createRouter({
  // Define your upload routes with validation
  imageUpload: s3
    .image()
    .max("10MB")
    .formats(["jpeg", "png", "webp"]),

  documentUpload: s3.file().max("50MB").types(["application/pdf", "application/msword"]),
});

export { router as POST };

// Export the router type for client-side type safety
export type Router = typeof router;

What's happening here? - s3.createRouter() creates a type-safe upload handler - s3.image() and s3.file() provide validation and TypeScript inference - The router automatically handles presigned URLs, validation, and errors - Exporting the type enables full client-side type safety

Create Upload Client

Create a type-safe client for your components using the recommended structured approach:

Recommended: The structured client provides the best developer experience with property-based access, centralized configuration, and enhanced type safety.

// lib/upload-client.ts
import { createUploadClient } from "pushduck/client";
import type { Router } from "@/app/api/s3-upload/route";

// Create a type-safe upload client (recommended)
export const upload = createUploadClient<Router>({
  endpoint: "/api/s3-upload",
});

Why this approach is recommended:

  • Full type inference from your server router
  • Property-based access - upload.imageUpload() instead of strings
  • IntelliSense support - see all available endpoints
  • Refactoring safety - rename routes with confidence
  • Centralized config - set headers, timeouts, and options once

Alternative: You can also use the hook-based approach if you prefer traditional React patterns:

// With type parameter (recommended)
const { uploadFiles } = useUploadRoute<AppRouter>('imageUpload')

// Or without type parameter (also works)
const { uploadFiles } = useUploadRoute('imageUpload')

The structured client is still recommended for most use cases.

Use in Your Components

Now you can use the upload client in any component with full type safety:

// components/image-uploader.tsx
"use client";

import { upload } from "@/lib/upload-client";

export function ImageUploader() {
  const { uploadFiles, uploadedFiles, isUploading, progress, error } =
    upload.imageUpload();

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files;
    if (files) {
      uploadFiles(Array.from(files));
    }
  };

  return (
    <div className="space-y-4">
      <div className="flex items-center space-x-4">
        <input
          type="file"
          multiple
          accept="image/*"
          onChange={handleFileChange}
          disabled={isUploading}
          className="file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
        />

        {isUploading && (
          <div className="flex items-center space-x-2">
            <div className="w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" />
            <span className="text-sm text-gray-600">
              Uploading... {Math.round(progress)}%
            </span>
          </div>
        )}
      </div>

      {error && (
        <div className="p-3 bg-red-50 border border-red-200 rounded-md">
          <p className="text-sm text-red-600">{error.message}</p>
        </div>
      )}

      {uploadedFiles.length > 0 && (
        <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
          {uploadedFiles.map((file) => (
            <div key={file.key} className="space-y-2">
              <img
                src={file.url}
                alt="Uploaded image"
                className="w-full h-32 object-cover rounded-lg border"
              />
              <p className="text-xs text-gray-500 truncate">{file.name}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Add to Your Page

Finally, use your upload component in any page:

// app/page.tsx
import { ImageUploader } from "@/components/image-uploader";

export default function HomePage() {
  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Upload Images</h1>
      <ImageUploader />
    </div>
  );
}

🎉 Congratulations!

You now have production-ready file uploads working in your Next.js app! Here's what you accomplished:

  • Type-safe uploads with full TypeScript inference
  • Automatic validation for file types and sizes
  • Progress tracking with loading states
  • Error handling with user-friendly messages
  • Secure uploads using presigned URLs
  • Multiple file support with image preview

What's Next?

Now that you have the basics working, explore these advanced features:

🎨 Enhanced UI

Add drag & drop, progress bars, and beautiful components

Image Upload Guide →

🔒 Custom Validation

Add authentication, custom metadata, and middleware

Router Configuration →

☁️ Other Providers

Switch to Cloudflare R2, DigitalOcean, or MinIO

Provider Setup →

⚡ Enhanced Client

Upgrade to property-based access for better DX

Migration Guide →

Need Help?

Loving Pushduck? Give us a ⭐ on GitHub and help spread the word!