Overview
Learn how to manage file uploads and storage with S3-compatible providers.
The Pro Next.js Prisma starter kit includes a robust storage service pre-configured for Cloudflare R2, but compatible with any S3-compatible service (AWS S3, DigitalOcean Spaces, MinIO, Supabase Storage, etc.).
Features
The storage service provides:
- Signed URLs for secure uploads and downloads
- Path validation to prevent security issues
- S3-compatible API supporting multiple providers
- Direct client uploads to reduce server load
- Type-safe storage operations
- Automatic URL generation for accessing files
Supported Providers
- Cloudflare R2 - Recommended, no egress fees
- AWS S3 - Industry standard
- DigitalOcean Spaces - Simple and affordable
- MinIO - Self-hosted S3-compatible storage
- Supabase Storage - Integrated with Supabase
- Any S3-compatible service
Setup
Upload Files
Access Files
How It Works
1. Server-Side: Generate Signed URLs
The storage service generates signed URLs on the server that allow clients to upload or download files directly:
import { createTRPCRouter, protectedProcedure } from '@/trpc/init';
import { TRPCError } from '@trpc/server';
import { storageConfig } from '@/config/storage.config';
import { getSignedUploadUrl } from '@/lib/storage';
export const storageRouter = createTRPCRouter({
signedUploadUrl: protectedProcedure
.input(z.object({ path: z.string(), bucket: z.string() }))
.mutation(async ({ input }) => {
if (input.bucket === storageConfig.bucketNames.images) {
const signedUrl = await getSignedUploadUrl(input.path, input.bucket);
return { signedUrl };
}
throw new TRPCError({ code: 'FORBIDDEN' });
})
});2. Client-Side: Direct Upload
The client uses the signed URL to upload directly to the storage provider:
'use client';
async function uploadFile(file: File) {
// Get signed URL from server
const { signedUrl } = await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({ path: 'avatars/user-1.png', bucket: 'images' })
}).then((res) => res.json());
// Upload directly to storage
await fetch(signedUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type }
});
}3. Access Files
Files are accessed through a file proxy route at /storage/[...path]:
'use client';
import { useMemo } from 'react';
import { storageConfig } from '@/config/storage.config';
export function useStorage(
image: string | undefined | null,
fallback?: string
): string | undefined {
return useMemo(() => {
if (!image) {
return fallback;
}
if (image.startsWith('http')) {
return image;
}
return `/storage/${storageConfig.bucketNames.images}/${image}`;
}, [image, fallback]);
}The /storage/[...path] route automatically generates signed URLs for file access and redirects to the storage provider.
Security
- Signed URLs: All uploads and downloads use time-limited signed URLs
- Path Validation: All file paths are sanitized to prevent traversal attacks
- CORS: Buckets are configured with CORS policies to allow uploads from your domain
- Access Control: Files are not publicly accessible by default - access is controlled through signed URLs
Storage Functions
The storage functions in lib/storage provide:
getSignedUploadUrl(path, bucket)- Generate signed URLs for uploadsgetSignedUrl(path, bucket, expiresIn)- Generate signed URLs for downloads- Automatic path validation to prevent security issues
Configuration
Storage is configured through environment variables:
S3_ACCESS_KEY_ID="your-access-key"
S3_SECRET_ACCESS_KEY="your-secret-key"
S3_ENDPOINT="https://your-endpoint.com"
S3_REGION="us-east-1"
S3_BUCKET="your-bucket-name"Bucket names are configured in config/storage.config.ts:
import { env } from '@/lib/env';
export const storageConfig = {
bucketNames: {
images: env.NEXT_PUBLIC_IMAGES_BUCKET_NAME ?? ''
}
} satisfies StorageConfig;Best Practices
- Use signed URLs - Never expose storage credentials to the client
- Validate paths - Always validate file paths to prevent security issues
- Set expiration times - Use appropriate expiration times for signed URLs
- Configure CORS - Set up CORS policies correctly for your domain
- Use appropriate buckets - Organize files into logical buckets
- Monitor usage - Keep track of storage usage and costs