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 Stripe PaymentIntent with idempotency key, stores order reference in memory (or database for production)
  • /app/api/webhook/route.ts — Verifies Stripe webhook signature, processes payment_intent.succeeded events, triggers downstream fulfillment logic
  • .env.local — Runtime secrets including NEXT_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 Authorization header
  • Response parsing: Extract variant IDs from the variants array; 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_cache or 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