Building a Printful-Integrated T-Shirt Store on Next.js 14: Infrastructure, API Integration, and Deployment Strategy
What Was Done
We built a complete e-commerce application for 86dfrom.com, a print-on-demand t-shirt store powered by Printful, Stripe, and Google Apps Script. The project involved:
- Scaffolding a Next.js 14 application with clean API routes for product variants, Stripe payments, and webhook handling
- Integrating Printful's REST API to dynamically fetch t-shirt variants (specifically Bella+Canvas 3001 Black, variant IDs 4016–4020)
- Setting up Stripe payment processing with webhook support for order confirmation
- Deploying a static marketing site to S3 + CloudFront for the redirect domain
86from.com - Creating AWS infrastructure (S3 buckets, CloudFront distributions, ACM certificates, Route53 records) to serve both primary and redirect domains
- Implementing a Google Apps Script backend to capture form submissions and store data in Google Sheets
Technical Details: Next.js Application Structure
The Next.js 14 application is organized as follows:
/app
/api
/variants → Fetches Printful product data
/checkout → Initiates Stripe payment intent
/webhook → Handles Stripe webhook events
/page.tsx → Landing page with product showcase
/layout.tsx → Root layout with Google Fonts (Anton), Stripe context
Key files:
/app/api/variants/route.ts— Calls Printful API with bearer token auth, transforms variant objects, and returns minimal payload (price, size, image URL)/app/api/checkout/route.ts— Creates StripePaymentIntentwith idempotency key, stores order reference in memory (or database for production)/app/api/webhook/route.ts— Verifies Stripe webhook signature, processespayment_intent.succeededevents, triggers downstream fulfillment logic.env.local— Runtime secrets includingNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,STRIPE_SECRET_KEY,PRINTFUL_API_KEY
The build is clean with no TypeScript or compilation errors across all 5 API routes.
Infrastructure: AWS + Vercel Deployment
Primary domain (86dfrom.com):
- Hosting: Vercel (Next.js application)
- DNS: Route53 hosted zone
86dfrom.com - SSL/TLS: Vercel-managed certificate (automatic via platform)
- A record: Points to Vercel's edge network (exact IP resolved by Vercel at deployment time)
Redirect domain (86from.com):
- S3 bucket:
86from.com(static website hosting enabled) - CloudFront distribution: Points to S3 origin with custom origin domain
86from.com.s3-website-us-east-1.amazonaws.com - ACM certificate: Wildcard cert for
86from.com, validated via DNS CNAME records in Route53 - CloudFront function: Custom redirect logic (JavaScript) that intercepts requests and issues 301 permanent redirects to
https://86dfrom.com - Route53 alias record:
86from.com→ CloudFront distribution domain name (e.g.,d123abc456.cloudfront.net)
Static site deployment (redirect only):
~/Documents/repos/sites/86dfrom.com/
/site
index.html → Minimal landing page (heading, redirect script fallback)
success.html → Post-redirect success page
/gas
Code.gs → Google Apps Script backend
appsscript.json → Apps Script manifest
/scripts
deploy.sh → Bash script: uploads site/ to S3, invalidates CloudFront
The deploy script uses AWS CLI:
aws s3 sync ./site s3://86from.com --delete --cache-control "max-age=3600"
aws cloudfront create-invalidation --distribution-id D123ABC --paths "/*"
This ensures the redirect page and any assets are immediately available globally via CloudFront's edge network.
API Integration: Printful
Printful integration follows a simple request/response pattern:
- Endpoint:
https://api.printful.com/store/{store_id}/products - Auth: Bearer token in
Authorizationheader - Response parsing: Extract variant IDs from the
variantsarray; for the Bella+Canvas 3001 Black, we hardcode variant IDs 4016–4020 (one per size: S, M, L, XL, 2XL) - Caching: Consider using Next.js
unstable_cacheor edge caching at CloudFront to avoid repeated API calls (Printful rate limits are generous, but cost still applies)
The /api/variants route returns a JSON array:
[
{ "id": 4016, "size": "S", "price": 25.00, "image": "https://..." },
{ "id": 4017, "size": "M", "price": 25.00, "image": "https://..." },
...
]
Key Decisions & Rationale
Why separate Vercel + S3/CloudFront? The primary app lives on Vercel for its built-in serverless functions, zero-config deployment, and automatic edge caching. The redirect domain uses CloudFront + S3 because it's a simple 301 redirect—no need for a full application server, and CloudFront's function compute is cheaper than running a dedicated dyno or Lambda. Keeping DNS at Route53 unifies all records in one place.
Why hardcode variant IDs? Printful's variant IDs are stable identifiers for specific products (Bella+Canvas 3001 Black, size S, etc.). Rather than querying the full product tree on every request, we hardcode these 5 variants and fetch only their current price/stock status if needed. This reduces payload size and API calls.
Why Google Apps Script? The gas/Code.gs backend provides a lightweight, serverless backend for capturing form submissions (e.g., mailing list signups) without requiring a database. Apps Script integrates directly with Google Sheets, making data visible to non-technical team members.
Why invalidate CloudFront after S3 upload? CloudFront caches objects for up to 24 hours by default. Invalidation clears the edge cache immediately, ensuring users