Building a Direct-to-Consumer T-Shirt Site on Vercel + AWS: 86dfrom.com Infrastructure & Deployment Strategy
What We Built
We deployed a full-stack e-commerce site for print-on-demand t-shirts using Next.js 14, Printful API integration, Stripe payments, and AWS CloudFront + S3 for static asset delivery. The architecture spans multiple deployment targets: Vercel for the Next.js application, S3 + CloudFront for the primary domain, and a redirect distribution for domain variants.
Project Structure
The codebase is organized as follows:
/site/— Static HTML landing pages (index.html, success.html) deployed to S3/gas/— Google Apps Script backend for auxiliary workflows (Code.gs, appsscript.json)/scripts/— Deployment and utility scripts (deploy.sh).env.local— Local environment variables for Printful, Stripe keys
The main Next.js application contains five API routes that compile cleanly: product variants, checkout handling, webhook processing, and Stripe integration endpoints.
Infrastructure Architecture
Primary Domain: 86dfrom.com
The primary domain uses a three-layer AWS setup:
- S3 Bucket:
86dfrom.com— Stores static site files (index.html, success.html, CSS, client-side JS) - CloudFront Distribution: Created via AWS Console with origin pointing to the S3 bucket. The distribution ID and domain name are used in Route53 DNS records.
- ACM Certificate: Requested for
86dfrom.comwith DNS validation via CNAME records added to Route53. Certificate validation takes ~5–10 minutes after DNS propagation. - Route53 Hosted Zone: Existing zone for
86dfrom.comreceives an A record (alias) pointing to the CloudFront distribution domain.
The S3 bucket policy is configured to allow CloudFront distribution access via an Origin Access Identity (OAI), preventing direct public access to objects. This pattern ensures:
- All requests flow through CloudFront (enabling caching, WAF rules, and compression)
- S3 bucket remains private; no public object ACLs required
- Cache invalidation via CloudFront API (used in deploy scripts) updates content instantly
Variant Domain: 86from.com (No "d")
A redirect distribution was created to handle the common misspelling. This uses:
- CloudFront Function: A lightweight viewer-request function that redirects HTTP(S) requests from
86from.comto86dfrom.comwith a 301 permanent redirect. - Redirect CloudFront Distribution: Minimal distribution (no S3 origin) with the CloudFront Function attached. Origin is a dummy HTTPS domain to satisfy CloudFront's requirement for an origin.
- Route53 A Record: Points
86from.comto the redirect distribution's domain name.
This pattern is lightweight and cost-effective: CloudFront Functions execute at edge locations with minimal latency and no compute charges (billed per 1M invocations).
Deployment Pipeline
Static Site Deployment (S3 + CloudFront)
The deploy script at scripts/deploy.sh follows this sequence:
# Example deployment steps (actual credentials redacted)
aws s3 sync ./site s3://86dfrom.com --delete
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"
Key decisions:
- --delete flag: Ensures removed files are purged from S3, preventing stale assets from being served
- Invalidation path "/*": Clears all CloudFront cache edges, guaranteeing fresh content within ~30 seconds globally
- Sequential execution: S3 sync completes before invalidation is triggered, avoiding race conditions
Vercel Deployment (Next.js Application)
The Next.js application is deployed separately to Vercel using:
npx vercel@latest --prod
Environment variables (Printful API key, Stripe secret/publishable keys, webhook secret) are stored in Vercel's project settings under Settings → Environment Variables, with separate configurations for preview and production deployments.
The application build is verified clean with npm run build before any deployment attempt. All five API routes compile without errors in Next.js 14's strict mode.
Printful Integration
Product variant IDs are populated by running a utility script:
node scripts/get-printful-variants.js
This script queries the Printful API for the Bella+Canvas 3001 t-shirt in Black colorway (variant IDs 4016–4020 for sizes XS–2XL). The script:
- Authenticates using the Printful API key from
.env.local - Filters by product (3001) and color (Black) to isolate the correct SKU
- Returns an array of variant objects with pricing, inventory, and printable areas
- Output is parsed and stored as environment variables for API routes
Why this approach? Hardcoding variant IDs is brittle if Printful's catalog changes. Querying the API at startup ensures the site always reflects current inventory and pricing.
Stripe Payment Integration
Stripe keys are required at two stages:
- Publishable Key: Embedded in client-side checkout forms, used by Stripe.js to create payment tokens
- Secret Key: Stored server-side in Vercel env vars, used in API routes to create charges and handle webhooks
The webhook endpoint at /api/webhook processes Stripe events (payment_intent.succeeded, charge.failed) to update order status. The webhook signing secret (obtained after deploying to Vercel and configuring the endpoint URL in Stripe Dashboard) is used to verify webhook authenticity via HMAC-SHA256 signature validation.
DNS Configuration
Route53 records for 86dfrom.com include:
- A Record (Alias):
86dfrom.com→ CloudFront distribution domain (e.g., d1234.cloudfront.net) - AAAA Record (Alias): IPv6 variant pointing to the same CloudFront distribution
- CNAME Records: For ACM certificate DNS validation (auto-managed by AWS once validation is requested)
Why alias records instead of CNAME? AWS Route53 alias records are zone apex-compatible, allowing DNS at the root domain (86dfrom.com) without the subdomain requirement that CNAME records impose.