Building a Multi-Domain T-Shirt Commerce Site with Next.js 14, Google Apps Script, and AWS CloudFront
This post documents the full-stack deployment of 86dfrom.com, a Printful-integrated t-shirt e-commerce site built with Next.js 14, backed by Google Apps Script for order processing, and fronted with AWS CloudFront + S3 for static hosting and CDN distribution. The project required coordinating multiple infrastructure layers: application deployment, serverless backend functions, DNS routing, SSL/TLS certificates, and CDN invalidation.
Architecture Overview
The final architecture consists of:
- Primary application: Next.js 14 deployed to Vercel (production environment at
/Users/cb/Documents/repos/sites/86dfrom.com) - Order backend: Google Apps Script deployed via Clasp, handling Stripe webhook ingestion and order routing to Printful API
- Static hosting: AWS S3 bucket (
86dfrom.comand86from.comredirect bucket) with CloudFront distributions for edge caching and HTTPS termination - DNS: Route53 hosted zone managing both apex and subdomain routing, with ACM certificate validation CNAMEs
- Payment processing: Stripe Checkout integration with webhook validation and secure key management via Vercel environment variables
Printful API Integration and Variant Population
The Printful integration required fetching product variant IDs to populate the Next.js form. The project stores credentials in .env.local (development) and Vercel's environment variable dashboard (production).
The variant fetching script scripts/get-printful-variants.js makes authenticated requests to the Printful API:
GET https://api.printful.com/store/{store_id}/products
Authorization: Bearer {PRINTFUL_API_KEY}
The script parses the response and extracts variant IDs for the Bella+Canvas 3001 blank in Black, storing them in the site's configuration. This approach avoids hardcoding variant IDs and makes the site portable across Printful stores.
Environment Configuration and Secrets Management
The .env.local file structure is:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
PRINTFUL_API_KEY=...
GOOGLE_APPS_SCRIPT_URL=https://script.google.com/macros/d/{DEPLOYMENT_ID}/userweb
Development uses a local .env.local file. For production, each variable was registered in Vercel's dashboard via:
vercel env add NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
vercel env add STRIPE_SECRET_KEY
vercel env add PRINTFUL_API_KEY
The NEXT_PUBLIC_ prefix exposes the Stripe publishable key to client-side code (safe—it's meant to be public). The secret key remains server-side only and is used in API routes for Stripe webhook verification and Printful requests.
Vercel Deployment Pipeline
The Next.js application was deployed to Vercel production via:
npx vercel@latest --prod
This command:
- Triggers a production build, running TypeScript compilation and Next.js optimization
- Deploys all five API routes:
/api/checkout,/api/webhook,/api/orders,/api/products, and a health check - Registers environment variables in the Vercel dashboard so they're injected at build and runtime
- Assigns a Vercel URL (
*.vercel.app) and waits for DNS propagation
After production deployment, DNS records for 86dfrom.com were configured to point to Vercel's nameservers, enabling the custom domain.
AWS Infrastructure: S3, CloudFront, and Route53
In parallel with the Vercel deployment, AWS resources were provisioned for the static CDN and domain management:
S3 Buckets
Two S3 buckets were created:
86dfrom.com— Primary bucket for static site assets (HTML, CSS, JS images). Bucket policy restricts access to CloudFront only via Origin Access Identity (OAI), preventing direct HTTP access.86from.com— Redirect bucket configured to redirect all requests to86dfrom.comvia HTTP 301 permanent redirects.
The S3 bucket policy for the primary bucket uses a deny-all default with an allow statement for the CloudFront OAI principal:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {OAI_ID}"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::86dfrom.com/*"
}
CloudFront Distributions
Two CloudFront distributions were created:
- Primary distribution (86dfrom.com)
- Origin:
86dfrom.com.s3.amazonaws.com - Default root object:
index.html - Error responses: 404 →
index.html(allows SPA-style routing) - Viewer protocol policy: Redirect HTTP to HTTPS
- Certificate: AWS Certificate Manager (ACM) cert for
86dfrom.com
- Origin:
- Redirect distribution (86from.com)
- Custom Lambda@Edge function deployed to viewer request stage
- Function returns HTTP 301 redirects to
https://86dfrom.com/path - Certificate: ACM cert for
86from.com
ACM Certificates and DNS Validation
Two ACM certificates were requested:
aws acm request-certificate \
--domain-name 86dfrom.com \
--domain-name www.86dfrom.com \
--validation-method DNS
ACM returns DNS validation records (CNAME pairs) that must be added to the domain's hosted zone. These were added to Route53:
aws route53 change-resource-record-sets \
--hosted-zone-id {ZONE_ID} \
--change-batch file://validation-records.json
ACM validates ownership by checking for these CNAMEs, then marks the certificate status as "Issued." This typically completes within minutes.
Route53 Hosted Zone and DNS Records
A Route53 hosted zone was created for 86dfrom.com with the