Building a Serverless Print-on-Demand T-Shirt Store: Next.js 14 + Printful + Stripe on AWS CloudFront
What Was Done
We built a complete serverless e-commerce site for 86dfrom.com—a Printful-integrated t-shirt store with Stripe payment processing. The architecture spans three layers: a Next.js 14 frontend deployed to Vercel, a Google Apps Script webhook handler for order fulfillment, and AWS CloudFront + S3 for static asset distribution and DNS management. The development session involved setting up environment configuration, establishing CI/CD patterns, and preparing infrastructure for production launch.
Technical Architecture
Frontend: Next.js 14 with App Router
The site uses Next.js 14's App Router with five core routes:
/— Landing page with product showcase/api/variants— Endpoint returning Printful variant IDs and pricing/api/webhook— Stripe webhook receiver for payment events/success— Order confirmation page/api/checkout— Stripe Checkout session creation (when needed)
The build compiles cleanly to static and serverless functions. Environment configuration lives in .env.local with the following required variables:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
The Printful integration targets the "86Store" account under the dangerouscentaur organization. Variant IDs for the Bella+Canvas 3001 Black t-shirt (4XL through 2XL) are fetched via scripts/get-printful-variants.js, which queries the Printful API and outputs variant mappings to site/variants.json.
Fulfillment: Google Apps Script Webhook Handler
File: gas/Code.gs
A Google Apps Script project handles webhook events from Stripe (and eventually Printful). This serverless function:
- Receives POST requests at a unique Google Apps Script deployment URL
- Validates Stripe webhook signatures using the webhook secret
- Parses payment intent events and writes order data to a Google Sheet
- Triggers order submissions to Printful via the Printful API
The appsscript.json manifest configures the script as a deployable web app, allowing unauthenticated POST requests. This pattern avoids serverless function cold starts and keeps fulfillment decoupled from the main site.
DNS and Static Delivery: AWS CloudFront + Route53
Two CloudFront distributions manage traffic:
- Primary 86dfrom.com distribution — Origins point to the Vercel deployment and an S3 bucket for static assets. CloudFront caching rules serve HTML with short TTLs and assets (JS, CSS, fonts) with long TTLs.
- Redirect 86from.com distribution — A CloudFront Function (Lambda@Edge replacement) redirects traffic from the old domain to the canonical 86dfrom.com.
Route53 hosted zone 86dfrom.com contains:
- Alias record pointing
86dfrom.com→ CloudFront distribution CNAME - CNAME records for ACM certificate validation (DNS-01 challenge)
- Alias record for
www.86dfrom.com(optional, can redirect to apex)
An ACM certificate covers both 86dfrom.com and *.86dfrom.com, issued via DNS validation.
Environment and Deployment
Local Development Setup
Project directory structure:
~/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html
│ ├── success.html
│ └── variants.json
├── gas/
│ ├── Code.gs
│ ├── appsscript.json
│ └── .clasp.json
├── scripts/
│ ├── deploy.sh
│ └── get-printful-variants.js
└── .env.local
The .env.local file is gitignored and populated manually with secrets. The scripts/deploy.sh handles S3 uploads and CloudFront cache invalidation:
#!/bin/bash
aws s3 sync ./site s3://86dfrom-static/ --delete
aws cloudfront create-invalidation \
--distribution-id E1A2B3C4D5E6F7 \
--paths "/*"
(Replace E1A2B3C4D5E6F7 with the actual CloudFront distribution ID.)
Vercel Deployment
The Next.js app deploys to Vercel with:
npx vercel@latest --prod
Environment variables are added to Vercel's project settings, not committed to git. The build process runs next build, which outputs static pages and API route handlers as serverless functions.
Key Architecture Decisions
Why Google Apps Script for Webhooks?
Instead of hosting webhooks on Vercel, we use Google Apps Script because:
- No cold starts: Google Apps Script has warm containers ready for requests.
- Decoupled from main site: Webhook failures don't affect product pages or checkout UX.
- Direct Google Sheets integration: Order logs are instantly written to a spreadsheet for team visibility.
- Lower cost: Free tier covers moderate order volumes; no Vercel function invocation charges.
Why CloudFront + S3 for Static Assets?
CloudFront provides:
- Global edge caching: Assets cached at 200+ PoPs worldwide, reducing origin bandwidth.
- HTTP/2 multiplexing: Faster asset delivery than serving directly from Vercel.
- Custom headers and redirects: CloudFront Functions allow domain redirects (86from.com → 86dfrom.com) without serverless overhead.
- DDoS protection: AWS Shield Standard included; optional Shield Advanced for L7 attacks.
Printful Integration Pattern
Variant IDs are fetched once during setup and cached in site/variants.json. This avoids API calls on every page load. The flow:
- Admin runs
scripts/get-printful-variants.js(manually or via CI/CD trigger) - Script queries Printful API for the 86Store, filters to Bella+Canvas 3001 Black sizes