Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14, Vercel, and AWS Multi-Domain Setup
This post documents the infrastructure and deployment strategy for 86dfrom.com, a Printful-integrated t-shirt e-commerce site. The project consolidates lessons from previous deployments (notably dangerouscentaur.com) and demonstrates a hybrid cloud approach: Next.js on Vercel for the application tier and AWS (S3 + CloudFront) for static asset distribution across multiple domain variants.
Project Overview
The 86dfrom project is a Next.js 14 application with five API routes handling product variants, cart operations, order creation, and Stripe webhook reconciliation. Unlike traditional monolithic Shopify deployments, this approach gives us:
- Direct Printful API integration — real-time variant inventory and pricing
- Custom Stripe payment flow — webhook-driven order fulfillment
- Multi-domain infrastructure — primary domain (86dfrom.com) + redirect domain (86from.com)
- Decoupled static hosting — CSS, fonts, and images via CloudFront for faster edge delivery
File Structure and Git Repository Layout
The development session migrated the project from a temporary Desktop location to the canonical repository structure:
~/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html (main landing page)
│ └── success.html (post-purchase confirmation)
├── gas/
│ ├── Code.gs (Google Apps Script functions, unused in final deploy)
│ ├── appsscript.json (GAS configuration)
│ └── .clasp.json (clasp CLI configuration)
├── scripts/
│ └── deploy.sh (S3 + CloudFront invalidation script)
└── .env.local (credentials — not in git)
The gas/ directory contains legacy Google Apps Script code left in place for reference but not deployed. The primary application lives in the Vercel project at 86dfrom-site (Next.js app), while static assets are deployed to S3 and served via CloudFront.
AWS Infrastructure: S3 Buckets and CloudFront Distributions
The session created two S3 buckets and three CloudFront distributions to handle the primary and redirect domains:
- Primary S3 bucket:
86dfrom-com(region: us-east-1)- Contains
site/index.htmlandsite/success.html - Bucket policy restricts access to CloudFront OAI (Origin Access Identity)
- No public-read ACL; all traffic flows through CloudFront
- Contains
- Redirect S3 bucket:
86from-com(region: us-east-1)- Empty bucket; serves only to trigger CloudFront redirect function
- Minimal configuration; the redirect logic lives in CloudFront
CloudFront Distribution 1: Primary (86dfrom.com)
Distribution ID: E2ABC1DEFGH2IJ (example)
Domain: d1234567890abc.cloudfront.net
DNS: 86dfrom.com ALIAS → CloudFront distribution
Origin: 86dfrom-com.s3.us-east-1.amazonaws.com
Cache behaviors:
- /index.html: TTL 60s (short; landing page changes frequently)
- /success.html: TTL 300s
- /css/*: TTL 86400s (24 hours; immutable assets)
ACM Certificate: 86dfrom.com (requested, validated via Route53 CNAME)
CloudFront Distribution 2: Redirect (86from.com)
Distribution ID: E3XYZ9KLMNO4PQ (example)
Domain: d9876543210xyz.cloudfront.net
DNS: 86from.com ALIAS → CloudFront distribution
Origin: 86from-com.s3.us-east-1.amazonaws.com
Function: redirect-to-86dfrom (CloudFront function, published to LIVE stage)
Behavior: All requests → redirect function → 301 to https://86dfrom.com$request.uri
ACM Certificate: 86from.com (requested, validated via Route53 CNAME)
The redirect function (deployed via CloudFront Functions API) intercepts all requests and returns a 301 permanent redirect, avoiding DNS-level redirects and maintaining SEO authority transfer.
DNS Configuration in Route53
Both domains are managed in a single Route53 hosted zone. The session created:
- 86dfrom.com A (alias): Points to CloudFront distribution (primary)
- 86dfrom.com AAAA (alias): IPv6 alias to same distribution
- 86from.com A (alias): Points to CloudFront distribution (redirect)
- 86from.com AAAA (alias): IPv6 alias to redirect distribution
- ACM validation CNAMEs: Two records (one per domain) for ACME challenge validation
Route53 alias records are preferred over traditional CNAME because they allow apex domain routing (no subdomain prefix required) and incur no extra charges.
Deployment Pipeline: From Desktop to Production
The session executed a multi-stage deployment:
Stage 1: Local Development & Build Validation
cd ~/Documents/repos/sites/86dfrom.com
npm install
npm run build # Confirms all 5 API routes compile cleanly
The clean build is critical — it validates that Printful API integration routes and Stripe webhook handlers have no syntax errors before pushing to production.
Stage 2: Static Asset Deployment
bash ~/Documents/repos/sites/86dfrom.com/scripts/deploy.sh
This script (modeled after the dangerouscentaur pattern in deploy_static.sh) performs:
aws s3 sync ./site s3://86dfrom-com/ --delete
aws cloudfront create-invalidation \
--distribution-id E2ABC1DEFGH2IJ \
--paths "/*"
The --delete flag ensures removed files don't linger in S3. The CloudFront invalidation clears all edge caches immediately, ensuring users see fresh content.
Stage 3: Vercel Application Deployment
npx vercel@latest --prod
This command (to be run after .env.local is populated) deploys the Next.js application to Vercel's production environment. Environment variables are injected via Vercel's dashboard or CLI, not committed to git.
Environment Variables and Secrets Management
The .env.local file (created locally, never committed) contains:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live