Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14, Google Apps Script, and AWS CDN Infrastructure
This post documents the technical implementation of 86dfrom.com, a custom t-shirt e-commerce site built on Next.js 14 with Printful fulfillment integration, Google Apps Script backend automation, and a multi-region CDN serving both the storefront and a redirect domain.
Architecture Overview
The project comprises three core components:
- Next.js 14 Application — storefront and checkout flow, deployed to Vercel
- Google Apps Script — order processing and fulfillment automation via Printful API
- AWS Infrastructure — S3 + CloudFront CDN for static assets and redirect handling; Route53 for DNS
This hybrid approach decouples the dynamic storefront (Vercel) from static asset delivery and domain routing (AWS), allowing independent scaling and failover.
Project Structure
The development workspace was organized as follows:
/Users/cb/Desktop/86dfrom/
├── site/
│ ├── index.html (redirect/landing fallback)
│ └── success.html (order confirmation page)
├── gas/
│ ├── Code.gs (Google Apps Script main handler)
│ └── appsscript.json (GAS manifest)
├── scripts/
│ └── deploy.sh (S3 + CloudFront deployment)
└── .env.local (secrets: Stripe, Printful, Webhook)
This mirrors the production layout at ~/Documents/repos/sites/86dfrom.com/, ensuring parity between development and deployment targets.
Printful Integration
Printful handles fulfillment for Bella+Canvas 3001 blank t-shirts (Black variant, sizes XS–3XL). The integration workflow:
- Fetch all available product variant IDs via Printful REST API
- Store variant IDs in environment variables (referenced in
.env.local) - Customer selects size → Next.js API route queries variant ID → passes to checkout
- Order created in Printful via webhook handler in Google Apps Script
The Printful API key was provisioned with full scopes across all stores in the dangerouscentaur account. Variant discovery is accomplished via the scripts/get-printful-variants.js script, which makes authenticated requests to https://api.printful.com/products and filters for the Black 3001 variant across size ranges 4016–4020.
Why this pattern? Storing variant IDs as environment constants avoids repeated API calls during checkout, reducing latency and API rate-limit exposure. Printful's variant catalog is stable; changes are rare and can be re-run on-demand.
Next.js 14 Build and Deployment
The Next.js application compiles cleanly with zero warnings. The build output includes five API routes:
pages/api/checkout.js— initiates Stripe payment intentpages/api/webhook.js— receives Stripe webhook events (payment confirmation)pages/api/create-order.js— writes order to Google Sheet for GAS processingpages/api/variants.js— returns available Bella+Canvas variants as JSONpages/api/health.js— liveness probe for deployment verification
Deployment to Vercel is straightforward: environment variables (Stripe secret key, Printful API key, Google Apps Script webhook URL) are configured in Vercel's project settings, then invoked via npx vercel@latest --prod. This deploys the Next.js app to Vercel's edge network, making it available at a stable Vercel domain (e.g., 86dfrom.vercel.app) until custom DNS is routed.
AWS Infrastructure: S3, CloudFront, Route53, and ACM
Static assets and domain redirect logic are hosted on AWS:
S3 Bucket for 86dfrom.com
A bucket named 86dfrom-com-web was created in us-east-1 with the following configuration:
- Block all public access disabled (objects require explicit public-read ACL or bucket policy)
- Bucket policy allows CloudFront origin access identity (OAI) to read all objects
- Static website hosting disabled (CloudFront is the only public interface)
- Versioning enabled for rollback capability
The bucket policy grants s3:GetObject only to the CloudFront OAI principal, preventing direct HTTP access and forcing requests through the CDN.
CloudFront Distribution for 86dfrom.com
Distribution ID: E1ABCD2EF3GH4I (example; actual ID varies). Configuration:
- Origin:
86dfrom-com-web.s3.us-east-1.amazonaws.com - Origin Access Identity: Restricts S3 bucket to CloudFront only
- Default Root Object:
index.html - Cache Behaviors:
/api/*→ no cache, proxy to Vercel origin/*.html→ 1-hour cache with Content-Type text/html/static/*→ 30-day immutable cache- Default → 24-hour cache
- Viewer Protocol Policy: Redirect HTTP → HTTPS
- HTTP/2 and HTTP/3: Enabled
Why this cache hierarchy? API routes must never cache (Stripe webhooks, order creation) to ensure freshness. HTML pages cache briefly to absorb traffic spikes without revalidation. Static assets use long TTLs since they're content-addressed (e.g., next-bundle-hash.js). This tiering reduces origin load while maintaining correctness.
SSL/TLS via ACM
An AWS Certificate Manager (ACM) certificate was requested for both 86dfrom.com and *.86dfrom.com. Validation used DNS CNAME records added to Route53:
_acmvalidation.86dfrom.com CNAME _validation.acm-validations.aws.
_acmvalidation.86dfrom.com CNAME _other-validation.acm-validations.aws. (for wildcard)
The certificate is attached to the CloudFront distribution, enabling HTTPS without paying for an additional SSL certificate (CloudFront handles TLS termination).
Route53 DNS Configuration
Two hosted zones manage domain resolution:
86dfrom.com(primary storefront) — A alias record pointing to CloudFront distributionE1