Home/Blog/How do I add signed uploads to an existing Next.js app?

How do I add signed uploads to an existing Next.js app?

To implement secure file uploads in Next.js with Uploadcare, use the Uploadcare signed uploads packages that come with HMAC-SHA256 authentication.

This requires the @uploadcare/react-uploader and @uploadcare/signed-uploads packages. The full implementation takes 4 steps.

Install Uploadcare in Next.js

Install the React uploader component (this provides the UI widget) and the signed uploads utility (generates cryptographic signatures):

npm install @uploadcare/react-uploader @uploadcare/signed-uploads

Set your environment variables in .env.local:

NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY=your_public_key_here
UPLOADCARE_SECRET_KEY=your_secret_key_here

You can find these keys in your Uploadcare dashboard under “API Keys”. The public key is used in the client uploader, while the secret key is only used on the server to generate signatures.

Basic file upload with a public key

Start with the simplest working example using FileUploaderRegular. Import from the /next path for SSR safety:

'use client';

import { FileUploaderRegular } from '@uploadcare/react-uploader/next';
import '@uploadcare/react-uploader/core.css';

export default function BasicUploader() {
  return (
    <FileUploaderRegular
      pubkey={process.env.NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY as string}
      onFileUploadSuccess={file => {
        console.log('File uploaded:', file);
      }}
    />
  );
}

This approach works immediately, but note that the public key alone allows any client to upload files since there’s no server-side validation.

Production-ready signed uploads

For production, implement server-signed uploads using HMAC-SHA256 authentication. This restricts uploads to requests with valid signatures.

First, create an API route that generates secure signatures:

// app/api/uploadcare-signature/route.ts

import { generateSecureSignature } from '@uploadcare/signed-uploads';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  try {
    await request.json();

    const { secureSignature, secureExpire } = generateSecureSignature(
      process.env.UPLOADCARE_SECRET_KEY as string || '',
      {
        expire: Date.now() + 30 * 60 * 1000, // 30 minutes
      },
    );

    return NextResponse.json({
      signature: secureSignature,
      expire: secureExpire,
    });
  } catch (error) {
    console.error('Error generating signature:', error);
    return NextResponse.json(
      { error: 'Failed to generate signature' },
      { status: 500 }
    );
  }
}

Then update your client component to fetch and use this signature:

'use client';

import { FileUploaderRegular } from '@uploadcare/react-uploader/next';
import '@uploadcare/react-uploader/core.css';
import { useEffect, useState } from 'react';

export default function SecureUploader() {
  const [secureSignature, setSecureSignature] = useState<{
    signature: string;
    expire: number;
  } | null>(null);

  useEffect(() => {
    const fetchSignature = async () => {
      const response = await fetch('/api/uploadcare-signature', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      });
      const data = await response.json();
      setSecureSignature(data);
    };

    fetchSignature();
  }, []);

  if (!secureSignature) {
    return <div>Loading...</div>;
  }

  return (
    <FileUploaderRegular
      pubkey={process.env.NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY as string}
      secureSignature={secureSignature.signature}
      secureExpire={secureSignature.expire}
      onFileUploadSuccess={file => {
        console.log('File uploaded securely:', file);
      }}
    />
  );
}

Common errors and troubleshooting

  • Problem: SSR errors with “document is not defined”
    Cause: Using the standard import path fails in server components
    Fix: Use @uploadcare/react-uploader/next and mark your component with 'use client'

  • Problem: Signature expires immediately
    Cause: Short expiries cause “expired signature” errors before users finish uploading, especially for large files
    Fix: Set expiry to at least 30 minutes in the future using a Unix timestamp in milliseconds (for example, Date.now() + 30 * 60 * 1000)

  • Problem: Component mounts with no signature prop
    Cause: The signature resolver must be provided synchronously
    Fix: Fetch signatures before rendering FileUploaderRegular, or use a loading state

Next steps

Check out how to implement rate limiting on your signature endpoint and webhook validation to process uploads automatically. If you deploy to Vercel, the serverless signed uploads guide covers that pattern end to end. For background on why this matters, see a guide to secure file uploads.

Further reading

Build file handling in minutesStart for free

Ready to get started?

Join developers who use Uploadcare to build file handling quickly and reliably.

Sign up for free