Storage

Storage

The storage module provides a shared object interface, presigned uploads, and image proxying. Choose S3 Compatible or a native Cloudflare R2 binding during generation.

Adapters

SelectionConfigurationRuntime
s3-compatibleSTORAGE_ENDPOINT, STORAGE_REGION, STORAGE_BUCKET_NAME, access key and secretServer, Docker, Vercel
r2-bindingNEXTDEVTPL_STORAGE R2 bindingCloudflare Workers

NEXT_PUBLIC_AVATARS_BUCKET_NAME selects the avatar bucket. Generic uploads default to nextdevtpl-uploads when no bucket is set; configure it explicitly for production.

Upload flow

  1. The browser requests upload data from /api/upload/presigned.
  2. The server checks the session, type, size, and object key.
  3. The S3 combination returns a presigned URL for a direct browser upload.
  4. The app stores the object key and reads through /image-proxy/... or a controlled URL.

The R2 binding is injected lazily during a Worker request while product code continues to call storageService.

Main code

PathPurpose
src/core/services/storage.tsShared object storage contract
src/adapters/storageS3 and R2 implementations
src/services/storage.tsSelected storage instance
src/app/api/upload/presigned/route.tsUpload entry point
src/app/image-proxy/[...path]/route.tsControlled image reads

Security checks

  • Keep buckets private by default and restrict CORS to application domains.
  • Validate type, size, and user scope before signing or writing.
  • Use unpredictable object keys and reject path traversal input.
  • Keep access keys server-side and scope R2 binding access to the required bucket.
  • Run pnpm test:run -- src/test/storage/security.test.ts after changes.

On this page