Building a Printful-Integrated T-Shirt Store with Next.js 14, Google Apps Script, and AWS CloudFront
This post documents the architecture and deployment pipeline for 86dfrom.com, a print-on-demand t-shirt storefront that integrates Printful's API, Stripe payments, and a custom Google Apps Script backend for order fulfillment. The project demonstrates a modern serverless approach to e-commerce, combining Next.js on Vercel, AWS S3/CloudFront for static hosting, and Google Workspace automation.
Project Architecture Overview
The 86dfrom.com stack consists of three independent but coordinated layers:
- Frontend: Next.js 14 application (Vercel) with Stripe checkout UI and Printful variant catalog
- Backend: Google Apps Script bound to Sheets, handling order persistence and fulfillment workflow
- Static Assets: S3 bucket with CloudFront distribution for brand assets and font delivery
This separation allows the payment flow (Vercel) to be independent from the fulfillment workflow (Apps Script), reducing vendor lock-in and enabling offline-capable data entry if needed.
File Structure and Deployment Targets
The project lives in two locations during development:
/Users/cb/Desktop/86dfrom/— working directory with live edits~/Documents/repos/sites/86dfrom.com/— canonical source for version control and CI/CD
Subdirectories:
site/— HTML/CSS/JS for static hosting (index.html, success.html)gas/— Google Apps Script source (Code.gs, appsscript.json)scripts/— Deployment and utility scripts (deploy.sh, get-printful-variants.js)
The Next.js application itself lives in the root, compiled to .next/ for Vercel deployment.
Environment Configuration Strategy
Rather than hardcoding credentials, the project uses a layered environment approach:
- .env.local: Development secrets (Stripe SK, Printful API key) — gitignored
- Vercel Dashboard → Settings → Environment Variables: Production secrets injected at build/runtime
- .env.example: Template showing required variables without values
Required variables for production:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
PRINTFUL_API_KEY=...
NEXT_PUBLIC_PRINTFUL_STORE_ID=86Store
The NEXT_PUBLIC_ prefix in Stripe's publishable key exposes it safely to the browser; the secret key remains server-only.
Printful Variant Population
Rather than hardcoding product IDs, scripts/get-printful-variants.js queries the Printful API to fetch live variant data for the Bella+Canvas 3001 Black t-shirt:
node scripts/get-printful-variants.js
This script:
- Authenticates with the Printful API key
- Lists products in the 86Store
- Extracts variant IDs for sizes XS through 3XL
- Outputs JSON suitable for importing into
lib/variants.json
Variants are stored centrally and referenced by both the frontend (for checkout) and API routes (for order creation). This decoupling means Printful catalog changes don't require code deploys.
API Route Architecture
The Next.js app exposes three critical endpoints:
- /api/variants: GET — Returns cached Printful variant list with pricing
- /api/checkout: POST — Creates a Stripe checkout session, logs to Sheets
- /api/webhook: POST — Receives Stripe payment_intent.succeeded events, triggers fulfillment
The /api/checkout route integrates with Google Apps Script via a webhook URL, ensuring every order attempt is logged for audit and recovery purposes. If Stripe fails after the order is recorded, the fulfillment script can retry.
Google Apps Script for Order Fulfillment
The gas/Code.gs file contains the serverless order handler. Key functions:
- doPost(): HTTP endpoint that receives JSON order data from the Vercel webhook
- submitOrderToPrintful(): Creates a draft order in Printful, captures variant ID and pricing
- logOrderToSheet(): Appends order details to a Google Sheet for manual review/fulfillment
- notifySlack(): Sends order alerts to a Slack channel (if enabled)
The appsscript.json manifest declares the script's OAuth scopes (Sheets API, Printful API) and webhook timeout (60 seconds).
Deployment to Google is handled by clasp push, which syncs gas/Code.gs to the bound script project. The .clasp.json file stores the project ID:
{
"scriptId": "1A2B3C4D5E6F..."
}
AWS Infrastructure for Static Assets
To reduce load on Vercel and serve brand assets with low latency, a separate S3 bucket was created:
- S3 Bucket: 86dfrom-com-assets (us-west-2)
- CloudFront Distribution: d1a2b3c4d5e6f7g8.cloudfront.net
- Route53 Alias: assets.86dfrom.com → CloudFront
The CloudFront distribution uses S3 origin with:
- Origin Access Control (OAC): Restricts bucket access to CloudFront only
- Caching Policy: 86400 seconds (1 day) for images, 3600 for HTML
- Compression: Gzip enabled for text/JSON payloads
The bucket policy explicitly denies public access, forcing all requests through CloudFront's DDoS protection and caching layer.
DNS and SSL/TLS Strategy
Two separate domain configurations were required:
- 86dfrom.com: Main site, points to Vercel nameservers (ns1.vercel.com etc.)
- 86from.com: Legacy typo domain, created as a CloudFront redirect to 86dfrom.com
An ACM certificate was issued for