Building a Vercel + AWS Multi-Domain T-Shirt Commerce Site: 86dfrom.com Deployment Architecture
This post documents the infrastructure and deployment strategy for 86dfrom.com, a Next.js 14 e-commerce site selling t-shirts via Printful and accepting payments through Stripe. The project spans three deployment targets: Vercel (main app), AWS S3 + CloudFront (static redirect domain), and Google Apps Script (order fulfillment webhook). Here's what we built and why.
Project Structure & Technology Stack
The application lives at /Users/cb/Documents/repos/sites/86dfrom.com/ with the following structure:
86dfrom.com/
├── site/ # Static HTML for redirect domain (86from.com)
│ ├── index.html # Landing page + product catalog
│ └── success.html # Post-purchase confirmation
├── gas/ # Google Apps Script backend
│ ├── Code.gs # Webhook receiver for order events
│ └── appsscript.json # GAS deployment manifest
├── scripts/
│ ├── deploy.sh # S3 + CloudFront deployment script
│ └── get-printful-variants.js # Fetch product variant IDs from Printful API
└── .env.local # Runtime environment variables (Stripe, Printful keys)
The Next.js application (separate repository, already deployed to Vercel) handles the dynamic product catalog and checkout flow. This project provides the static fallback site and fulfillment infrastructure.
AWS Infrastructure: S3 + CloudFront + Route53 + ACM
Primary Domain (86dfrom.com):
- S3 Bucket:
86dfrom.com(us-east-1) — holds compiled Next.js static exports - CloudFront Distribution: Maps to S3, caches aggressively, serves HTTPS via ACM certificate
- ACM Certificate: Wildcard cert for
*.86dfrom.com+ root domain, auto-renewing - Route53 Hosted Zone:
86dfrom.com— contains A records aliasing to CloudFront distribution
Redirect Domain (86from.com):
- S3 Bucket:
86from.com(us-east-1) — stores onlyindex.htmlwith redirect logic - CloudFront Distribution (Redirect): Custom function intercepts requests, rewrites to primary domain
- ACM Certificate: Separate cert for
86from.com(typo-friendly domain) - Route53 Record: CNAME from
86from.comto redirect CloudFront distribution
Why S3 + CloudFront instead of Vercel? We needed a clean typo-domain redirect (86from.com → 86dfrom.com) with HTTPS at the edge. CloudFront Functions allow request-time rewrites without backend logic. Also, S3 provides a cheap static origin, and CloudFront's global edge locations guarantee sub-100ms redirects.
Deployment Pipeline
The deployment script (scripts/deploy.sh) orchestrates the full pipeline:
#!/bin/bash
# Example deployment flow (simplified, no credentials shown)
# 1. Build Next.js app (if applicable)
npm run build
# 2. Upload to S3
aws s3 sync ./site s3://86dfrom.com --delete --cache-control max-age=31536000
# 3. Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id E2ABC123XYZ \
--paths "/*"
# 4. Verify DNS resolution
dig 86dfrom.com
This script is executed via:
bash ~/Documents/repos/sites/86dfrom.com/scripts/deploy.sh
Each deployment:
- Uploads all files in
site/to the primary S3 bucket with aggressive cache headers (1-year TTL for static assets) - Invalidates the entire CloudFront distribution cache to force immediate content refresh
- Verifies domain resolution before exit
Printful Integration & Variant Management
The Next.js application dynamically fetches product variants from Printful's API at build/runtime. The script scripts/get-printful-variants.js queries the Printful API:
// Pseudocode: get-printful-variants.js
const printfulKey = process.env.PRINTFUL_API_KEY;
async function getVariants() {
const response = await fetch(
'https://api.printful.com/store/86Store/products',
{ headers: { 'Authorization': `Bearer ${printfulKey}` } }
);
const products = await response.json();
// Extract variant IDs for Bella+Canvas 3001 Black
products.forEach(p => {
p.variants.forEach(v => {
if (v.name.includes('Black')) {
console.log(`${v.id}`);
}
});
});
}
This populates .env.local with variant IDs needed for dynamic pricing and inventory checks. Printful's API key (granted admin scope to the 86Store) is stored securely in environment variables, never committed to Git.
Stripe Payment Integration & Webhooks
The Next.js application exposes /api/checkout/sessions (POST) to create Stripe checkout sessions and /api/webhook to receive order-complete events. The webhook:
- Validates the request signature using Stripe's shared secret (
whsec_...) - Extracts customer email and order details from
checkout.session.completedevents - Forwards the order to Google Apps Script via HTTP POST for fulfillment
The Google Apps Script endpoint (gas/Code.gs) runs on a Google-managed domain, receives the POST, parses the order, and logs it to a private Google Sheet for manual fulfillment. This keeps customer data out of public logs and allows non-technical staff to manage orders via spreadsheet.
Environment Variables & Secrets Management
The .env.local file (not committed) contains:
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020
GAS_WEBHOOK_URL=https://script.google.com/macros/d/...
In Vercel, these are stored in Project Settings → Environment Variables with separate test/production values. The Vercel CLI pulls them during build: