This project uploads images to Amazon S3 with a pre-signed POST URL and serves them through Amazon CloudFront for lower global latency.
- Next.js 15 (App Router)
- React 19
- S3 upload API route (
src/app/api/route.ts) - CloudFront delivery URL in API response (
cdnObjectUrl) - Built-in browser benchmark demo on the homepage to compare
S3 DirectvsCloudFront CDN
- User selects a file in the UI.
- Frontend calls
/api?filename=...&contentType=.... - API returns:
uploadUrl+fields(for S3 pre-signed POST)s3ObjectUrl(direct S3 object URL)cdnObjectUrl(CloudFront URL, ifCLOUDFRONT_DOMAINis configured)
- Frontend uploads directly to S3.
- Frontend previews the uploaded image from CloudFront and can run a latency demo.
- AWS account
- S3 bucket
- CloudFront distribution (origin = S3 bucket)
- Node.js 20+
Create an IAM user with least-privilege S3 permissions for your upload bucket.
- Keep bucket in your preferred region.
- For this demo, objects are uploaded with
public-readACL. - Add bucket CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": []
}
]- Origin: your S3 bucket
- Viewer protocol policy: Redirect HTTP to HTTPS
- Allowed methods:
GET, HEAD, OPTIONS - Cache policy: managed cache policy (or custom with reasonable TTL)
- Enable compression
- Copy your distribution domain (example:
d111111abcdef8.cloudfront.net)
Copy .env.example to .env.local and update values.
cp .env.example .env.localRequired values:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_REGIONAWS_BUCKET_NAMECLOUDFRONT_DOMAIN(withouthttps://)
npm install
npm run devOpen http://localhost:3000.
The home page includes a Run CDN demo button.
- Upload one image.
- Show side-by-side previews:
- CloudFront URL
- Direct S3 URL
- Click Run CDN demo.
- The app performs 5 fetches against each source and prints sample timings + average.
- Run it twice:
- First run warms cache.
- Second run usually shows stronger CloudFront advantage.
- CloudFront serves from edge locations closer to end users.
- Repeated requests benefit from edge cache hits.
- Direct S3 always goes to bucket region, increasing latency for distant users.
{
"uploadUrl": "https://your-bucket.s3.eu-central-1.amazonaws.com",
"fields": {
"key": "uploads/1710000000000-photo.jpg",
"acl": "public-read",
"Content-Type": "image/jpeg"
},
"objectKey": "uploads/1710000000000-photo.jpg",
"s3ObjectUrl": "https://your-bucket.s3.eu-central-1.amazonaws.com/uploads/1710000000000-photo.jpg",
"cdnObjectUrl": "https://d111111abcdef8.cloudfront.net/uploads/1710000000000-photo.jpg"
}- The benchmark runs in the browser and reflects your current network location.
- For production, prefer private S3 + CloudFront Origin Access Control and signed URLs/cookies where needed.
- If you overwrite object keys, consider versioned keys or invalidations for freshness.