Building a Printful-Integrated T-Shirt E-Commerce Site: Next.js 14, Google Apps Script, and AWS Infrastructure
What Was Done
I completed the foundational infrastructure and deployment pipeline for 86dfrom.com, a print-on-demand t-shirt storefront powered by Printful, Stripe, and Google Apps Script. The project spans three environments: a Next.js 14 frontend (built and validated), a Google Apps Script backend for order management, and AWS infrastructure (S3 + CloudFront + Route53) for static asset hosting and DNS routing.
The session involved:
- Verifying a clean Next.js 14 build with 5 compiled API routes
- Provisioning AWS S3 buckets and CloudFront distributions for both 86dfrom.com and a redirect domain (86from.com)
- Setting up ACM certificates and Route53 DNS records for two domains
- Fetching Printful variant IDs for Black Bella+Canvas 3001 t-shirts
- Creating a deployment script (deploy.sh) that syncs site files to S3 and invalidates CloudFront
- Establishing a secure credential management pattern using shared secrets files
Technical Details: Project Structure and Build Pipeline
The 86dfrom project is organized into three discrete components:
- /site – Static HTML frontend (index.html, success.html). Served via CloudFront + S3.
- /gas – Google Apps Script backend (Code.gs, appsscript.json). Deployed separately via clasp CLI.
- /scripts – Automation tooling: deploy.sh handles S3 sync and CloudFront invalidation.
The Next.js codebase (verified with npm run build) compiles cleanly with no errors. Key routes include:
/api/variants– Returns Printful variant IDs and metadata/api/order– Submits orders to Printful's API/api/webhook– Stripe webhook endpoint (not yet configured)- Standard product and checkout pages
Environment variables are centralized in .env.local:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
NEXT_PUBLIC_PRINTFUL_STORE_ID=...
GOOGLE_APPS_SCRIPT_DEPLOYMENT_ID=...
Infrastructure: AWS Architecture
S3 Buckets:
Created 86dfrom.com and 86from.com buckets with static website hosting enabled. Both are configured for public read access via bucket policies that grant s3:GetObject to CloudFront origin access identity (OAI). This pattern isolates the bucket from direct HTTP access—all traffic flows through CloudFront, enabling caching, WAF integration, and origin shielding.
CloudFront Distributions:
- 86dfrom.com distribution – Primary domain. Origin points to S3 bucket; cache behavior set to cache HTML with 0 TTL and static assets (CSS, JS, WOFF2) with 86400 TTL. Viewer protocol policy enforces HTTPS. Compression enabled for text assets.
- 86from.com distribution – Redirect-only. Uses CloudFront Functions to issue HTTP 301 redirects from 86from.com → 86dfrom.com. This centralizes traffic on the canonical domain and preserves SEO equity via permanent redirects.
ACM Certificates:
Requested ACM certificates for both domains via DNS validation. Route53 hosted zones were queried to identify the correct zone ID, then CNAME validation records were automatically added. Certificate validation typically completes within 5–15 minutes.
Route53 DNS:
Two A records (alias records pointing to CloudFront distributions):
86dfrom.com→ CloudFront distribution for 86dfrom.com86from.com→ CloudFront distribution for 86from.com (redirects to primary)
Deployment Pipeline: deploy.sh
The scripts/deploy.sh script automates the static site update process:
#!/bin/bash
set -e
BUCKET="86dfrom.com"
DISTRIBUTION_ID="E..." # CloudFront distribution ID
SITE_DIR="site"
# Sync HTML and assets to S3, deleting old files
aws s3 sync "$SITE_DIR" "s3://$BUCKET/" \
--delete \
--cache-control "max-age=0,no-cache" \
--exclude "*.css" --exclude "*.js" --exclude "*.woff2"
# Sync static assets with long TTL
aws s3 sync "$SITE_DIR" "s3://$BUCKET/" \
--cache-control "max-age=86400,public" \
--include "*.css" --include "*.js" --include "*.woff2"
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id "$DISTRIBUTION_ID" \
--paths "/*"
echo "Deployment complete."
This two-pass approach balances flexibility (HTML updates are cache-busted immediately) with performance (static assets remain cached for 24 hours, reducing origin load).
Printful Integration
Fetched variant IDs for Bella+Canvas 3001 Black t-shirts across all available sizes using Printful's REST API. The token is scoped to the "86Store" store on the dangerouscentaur.com Printful account. Variant IDs (4016–4020) are hard-coded in the Next.js /api/variants route and used during order submission to /api/order.
Key Decisions and Rationale
Why CloudFront for a static site? CloudFront provides global edge caching, HTTPS termination, and DDoS protection via AWS Shield Standard—all benefits even for simple HTML sites. The redirect distribution (86from.com) is essentially free once the primary distribution is in place.
Why separate cache TTLs? Separating HTML (no cache) from static assets (24-hour cache) allows rapid content updates without sacrificing performance. Modern asset pipelines use content hashing, so static assets rarely change.
Why Route53 alias records? AWS alias records integrate tightly with CloudFront and carry no extra cost. They also support health checks and latency-based routing if needed later.
Why two domains? The 86from.com redirect centralizes traffic on the canonical domain (86dfrom.com). This prevents split link equity in SEO and simplifies analytics. CloudFront Functions handle the redirect at the edge, avoiding a redundant origin request.
What's Next
- Vercel Deployment: Deploy Next.