Building a Serverless Print-on-Demand T-Shirt Store: 86dfrom.com Infrastructure & Deployment
This post documents the complete technical architecture and deployment of 86dfrom.com, a serverless print-on-demand t-shirt marketplace built on Next.js 14, Printful, Stripe, and AWS infrastructure. We'll walk through the infrastructure decisions, deployment strategy, and integration patterns used to bring this project from local development to production.
Project Architecture Overview
The 86dfrom project is structured as a monorepo with three primary components:
- Next.js 14 Frontend & API (
/site): Hosted on Vercel, handles product display, cart logic, and Stripe webhooks - Google Apps Script Backend (
/gas): Order fulfillment automation via Google Sheets integration - Deployment Scripts (
/scripts): Infrastructure-as-code for reproducible builds
The stack uses a hybrid cloud approach: Vercel for dynamic frontend/API, AWS CloudFront + S3 for static assets, Route53 for DNS, and ACM for SSL/TLS certificates.
Directory Structure & Configuration Files
The project is organized as follows:
/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html (static landing, populated via build)
│ └── success.html (post-purchase confirmation)
├── gas/
│ ├── Code.gs (Google Apps Script for order processing)
│ └── appsscript.json (manifest with sheet/Drive scopes)
├── scripts/
│ └── deploy.sh (S3 upload + CloudFront invalidation)
└── .env.local (runtime secrets - not in repo)
The .env.local file contains environment variables for Stripe keys, Printful API tokens, and Google Sheets IDs. This file is excluded from version control and injected at build/deploy time via Vercel's environment variable dashboard.
AWS Infrastructure: S3, CloudFront, Route53, and ACM
S3 Bucket Configuration
We created a dedicated S3 bucket named 86dfrom-com-static to serve static assets (CSS, fonts, images) with CloudFront as the CDN layer. This bucket is configured with:
- Block all public access disabled (CloudFront has exclusive access via bucket policy)
- Bucket policy restricting reads to the CloudFront Origin Access Identity (OAI)
- No website hosting enabled (CloudFront handles HTTP/HTTPS routing)
The bucket policy follows the principle of least privilege:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity [OAI_ID]"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::86dfrom-com-static/*"
}
]
}
CloudFront Distribution for Primary Domain
Two CloudFront distributions were provisioned for domain coverage:
- Primary distribution for 86dfrom.com: Origin configured to
86dfrom-com-static.s3.amazonaws.comwith OAI, HTTP/2, brotli compression enabled, and a default TTL of 86400 seconds (24 hours) for HTML, 31536000 seconds (1 year) for versioned assets. - Redirect distribution for 86from.com (typo domain): Uses a CloudFront Function to redirect all traffic to the canonical domain with HTTP 301 status. This function was published via the CloudFront console at
86from-redirect.
ACM certificates were provisioned for both domains (86dfrom.com and 86from.com) via DNS validation using Route53 CNAME records. The validation process involved:
# Request ACM cert (DNS validation)
# AWS Console → Certificate Manager → Request Certificate
# Add domain: 86dfrom.com, 86from.com
# Validation: DNS - copy the CNAME records provided
# Add CNAME records to Route53 for DNS validation
# AWS Console → Route53 → Hosted Zone → Create Record
# Name: _[token].86dfrom.com
# Type: CNAME
# Value: _[validation-token].acm-validations.aws
Route53 DNS Configuration
Route53 was configured with A records pointing to the respective CloudFront distributions:
86dfrom.com→ Primary CloudFront distribution (alias record, no charge for Route53 queries)86from.com→ Redirect CloudFront distribution (alias record pointing to redirect function distribution)
Route53 alias records leverage AWS's internal routing without consuming DNS queries, reducing operational overhead.
Next.js 14 Deployment to Vercel
The Vercel deployment uses the following configuration:
# Deploy to Vercel production
npx vercel@latest --prod
# Environment variables injected via Vercel dashboard:
# NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
# STRIPE_SECRET_KEY
# PRINTFUL_API_KEY
# GOOGLE_SHEETS_ID
# GOOGLE_SERVICE_ACCOUNT_JSON
Vercel automatically handles:
- Git integration with automatic deployments on push to main branch
- SSL/TLS certificate provisioning via Let's Encrypt
- DDoS protection and WAF rules
- Automatic image optimization via Next.js Image component
- Edge Functions for A/B testing and geolocation-based routing (not used in this project, but available)
The build command is configured to run the Next.js build process, which compiles all routes and outputs static assets for the Vercel Edge Network.
Stripe Integration & Webhook Configuration
The Stripe integration is split between client-side and server-side:
- Client:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYinjected into/site/index.htmlfor client-side Stripe.js initialization - Server:
STRIPE_SECRET_KEYused in/api/webhookroute to verify webhook signatures and process payments - Webhook: Endpoint at
https://86dfrom.com/api/webhookconfigured in Stripe Dashboard to receive events:payment_intent.succeeded,payment_intent.payment_failed,charge.refunded
Webhook verification uses HMAC-SHA256 signing with the webhook secret, ensuring only authentic Stripe events trigger order fulfillment logic.
Printful API Integration
The Printful integration fetches product variant IDs (for the Bella+Canvas 3001 Black t-shirt) and stores them in .env