Building a Vercel + AWS Multi-Domain T-Shirt Commerce Site: Infrastructure Setup for 86dfrom.com
What Was Done
We built out the complete infrastructure stack for 86dfrom.com, a Next.js 14 t-shirt e-commerce site integrated with Printful for print-on-demand fulfillment and Stripe for payments. This involved:
- Verifying a clean Next.js 14 build with 5 API routes (product variants, checkout, webhook)
- Provisioning AWS S3 buckets and CloudFront distributions for both
86dfrom.comand a redirect domain (86from.com) - Configuring Route53 DNS with ACM certificate validation
- Setting up Google Fonts caching (Anton woff2) and Stripe/Printful API integration
- Creating deployment automation via bash scripts and Google Apps Script
Technical Details: Project Structure
The project is organized into three core directories:
/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html # Main landing page (SSG-deployed to S3)
│ └── success.html # Post-purchase confirmation page
├── gas/
│ ├── Code.gs # Google Apps Script (form processing, logging)
│ └── appsscript.json # GAS manifest
└── scripts/
└── deploy.sh # Bash deployment orchestration
The Next.js application itself lives in /Users/cb/Desktop/86dfrom/ during development and compiles to a static build for serverless deployment on Vercel. The split between static assets (S3/CloudFront) and dynamic APIs (Vercel functions) allows us to optimize CDN behavior per content type.
Infrastructure: AWS + Vercel Architecture
S3 Bucket Creation
We created two S3 buckets in the AWS account to isolate production traffic:
86dfrom.com– Production site assets (HTML, CSS, images)86from.com– Redirect-only bucket (301 to primary domain)
Why two buckets? Keeping redirect traffic separate prevents accidental overwrites to production assets and simplifies access control policies. Each bucket is configured with:
- Block public ACLs enabled (all access via CloudFront OAI)
- Bucket policies restricting read access to CloudFront Origin Access Identity
- Static website hosting disabled (CloudFront is the only entry point)
CloudFront Distributions
Two CloudFront distributions were created:
- 86dfrom.com distribution – Primary: S3 origin, caching for index.html and static assets, default TTL 86400s (1 day)
- 86from.com distribution – Redirect: CloudFront Function (viewer request) that returns 301 to
https://86dfrom.com
The redirect function (published via CloudFront Functions API) intercepts all requests before hitting any origin:
// CloudFront Function: viewer request
function handler(event) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': { value: 'https://86dfrom.com' }
}
};
}
Why CloudFront Functions over Lambda@Edge? Lower latency, no cold starts, immediate global propagation, and sufficient for simple redirects. Lambda@Edge would be overkill here.
ACM Certificate Management
Two ACM certificates were requested in us-east-1 (required for CloudFront):
86dfrom.com– DNS validation via Route53 CNAME86from.com– DNS validation via Route53 CNAME
Validation records were added to Route53 hosted zone 86dfrom.com (both domains share the same zone for simplicity). Certificate status was polled until validation completed before CloudFront distribution creation.
Route53 DNS Configuration
The hosted zone 86dfrom.com contains:
86dfrom.com A ALIAS→ CloudFront distribution (primary)86from.com A ALIAS→ CloudFront distribution (redirect)- ACM validation CNAMEs (temporary, cleaned up post-validation)
Both records use ALIAS routing (AWS-specific, no charge for Route53 queries) rather than CNAME records, allowing the apex domain to resolve properly.
Deployment Pipeline
S3 Deployment Script
The scripts/deploy.sh handles asset syncing:
#!/bin/bash
aws s3 sync ./site s3://86dfrom.com/ --delete
aws cloudfront create-invalidation --distribution-id E1234ABCD --paths "/*"
The --delete flag removes stale files from S3, and the CloudFront invalidation clears the edge cache immediately (important for rapid iterations). Distribution IDs are hardcoded for simplicity; in production, these would be stored in a config file or environment variable.
Google Apps Script Automation
The gas/Code.gs file contains utility functions for form submissions and logging. The appsscript.json manifest declares project dependencies and deployment settings. This layer allows us to process form data server-side without exposing API endpoints directly to the client.
Key Decisions & Rationale
Static Site + Serverless APIs
Rather than deploying a full Next.js server, we're splitting responsibilities:
- Static assets (HTML, CSS, fonts) → S3 + CloudFront for ultra-fast delivery and lower compute cost
- Dynamic APIs (variant fetching, checkout, webhooks) → Vercel Functions (serverless Node.js)
This hybrid approach optimizes cost (S3 + CloudFront is ~$0.085/GB transferred; Vercel Functions are $0.50/1M invocations) and latency (static assets hit the edge; APIs have warm containers).
Multi-Domain Architecture
By provisioning 86from.com as a redirect-only domain, we capture typo traffic and misspellings without duplicating content or confusing SEO. The CloudFront Function approach avoids any origin requests—pure edge logic.
Printful Integration
The Printful API key (scoped to all stores under the dangerouscentaur.com account) is stored in .env.local and used by the /api/variants endpoint. This endpoint is called client-side to fetch variant IDs (4016–4020 for Bella+Canvas 3001 Black t-shirts), enabling the UI to render size/color options dynamically.