Cloudflare R2 for static assets and Cloudflare Images for dynamic image delivery, including presigned upload URLs and the file download proxy.
The platform uses two Cloudflare services for media delivery:
Cloudflare R2
Object storage for static assets (logos, icons, audio). Served via a custom domain at cdn.njrajatmahotsav.com. Accessed with the AWS S3-compatible SDK.
Cloudflare Images
Dynamic image delivery with automatic format conversion and pre-defined quality variants. Images are referenced by an opaque image ID, not a filename.
# Cloudflare R2 — used by presigned URL generation (server-side only)R2_ENDPOINT=https://<account-id>.r2.cloudflarestorage.comR2_ACCESS_KEY_ID=your-r2-access-key-idR2_SECRET_ACCESS_KEY=your-r2-secret-access-keyR2_BUCKET_NAME=your-bucket-nameR2_BUCKET_PREFIX=submissions/
R2_ENDPOINT, R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY are server-only variables (no NEXT_PUBLIC_ prefix). They are never sent to the browser. The CDN base URL and Cloudflare Images base URL are hardcoded in lib/cdn-assets.ts because they are public by design.
Cloudflare Images serves variants of each image. All helpers append ?format=auto so Cloudflare negotiates WebP or AVIF based on the browser’s Accept header.
lib/cdn-assets.ts
const CLOUDFLARE_IMAGES_BASE = 'https://imagedelivery.net/vdFY6FzpM3Q9zi31qlYmGA/'// Standard quality — use for most imagesexport const getCloudflareImage = (imageId: string) => `${CLOUDFLARE_IMAGES_BASE}${imageId}/bigger?format=auto&quality=90`// Mobile wallpaper variant — optimised for narrow viewportsexport const getCloudflareImageMobileWp = (imageId: string) => `${CLOUDFLARE_IMAGES_BASE}${imageId}/mobileWP?format=auto&quality=90`// Maximum quality — use for hero images or OG imagesexport const getCloudflareImageBiggest = (imageId: string) => `${CLOUDFLARE_IMAGES_BASE}${imageId}/biggest?format=auto&quality=100`// Raw R2 path helper — for files stored in R2 by pathexport const getR2Image = (filename: string) => `${CDN_BASE_URL}${filename}`
File uploads (seva submissions) never go directly through the Next.js server. Instead, the browser requests a short-lived presigned URL from a Route Handler, then uploads the file directly to R2 using that URL.The S3 client is initialised once per cold start using R2’s S3-compatible endpoint:
POST /api/generate-cs-personal-submision-upload-urls
Generates one presigned PUT URL per file. URLs expire after 600 seconds.Request body:
{ submissionId: string // Unique ID for this submission activityName: string // Name of the seva activity files: Array<{ name: string // Original filename type: string // MIME type (e.g. "image/jpeg") }> folderName: string // Top-level folder in the bucket}
Response:
{ uploadUrls: Array<{ url: string // Presigned PUT URL — upload directly from the browser key: string // Object key in the bucket filename: string // Original filename }>}
After receiving the presigned URLs, use a standard fetch with method: 'PUT' and the file as the body. Set the Content-Type header to match the type you sent in the request.
GET /api/download proxies file downloads from allowed CDN domains. It exists to force a Content-Disposition: attachment header — browsers do not allow cross-origin downloads via an anchor tag directly.Query parameters:
Do not remove or loosen the domain allowlist. The download proxy would otherwise be abusable as an open redirect or SSRF vector against internal services.