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
| Selection | Configuration | Runtime |
|---|---|---|
s3-compatible | STORAGE_ENDPOINT, STORAGE_REGION, STORAGE_BUCKET_NAME, access key and secret | Server, Docker, Vercel |
r2-binding | NEXTDEVTPL_STORAGE R2 binding | Cloudflare 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
- The browser requests upload data from
/api/upload/presigned. - The server checks the session, type, size, and object key.
- The S3 combination returns a presigned URL for a direct browser upload.
- 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
| Path | Purpose |
|---|---|
src/core/services/storage.ts | Shared object storage contract |
src/adapters/storage | S3 and R2 implementations |
src/services/storage.ts | Selected storage instance |
src/app/api/upload/presigned/route.ts | Upload entry point |
src/app/image-proxy/[...path]/route.ts | Controlled 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.tsafter changes.