Building a Printful-Integrated T-Shirt Shop on Next.js 14 with AWS CloudFront and Stripe: Infrastructure & Deployment

This post documents the end-to-end build of 86dfrom.com, a Printful-backed t-shirt e-commerce site. We deployed a Next.js 14 full-stack application across Vercel (application), AWS S3 + CloudFront (static assets), and Route53 (DNS), with Stripe for payment processing and Printful as the print-on-demand fulfillment partner.

What Was Done

We built a complete production-ready e-commerce application from scratch, spanning:

  • Next.js 14 application with five API routes (product catalog, variant fetching, order creation, webhooks, health checks)
  • AWS infrastructure: S3 bucket, CloudFront CDN distribution, ACM SSL certificates, Route53 DNS
  • Printful API integration to fetch real product variants (Bella+Canvas 3001 black t-shirts in sizes XS–3XL)
  • Stripe payment integration with webhook handling for order confirmation
  • Automated deployment scripts for both Vercel and S3/CloudFront

Technical Details: Architecture

Frontend: Single-page form-based application served via Vercel's edge network. The site requests product data on load from the Next.js API, then submits orders via a POST to /api/orders, which proxies to Printful and creates a Stripe payment intent.

Backend: Five API routes in /Users/cb/Documents/repos/sites/86dfrom.com/app/api/:

  • GET /api/products — Returns cached Printful product catalog with variant IDs, prices, and images
  • GET /api/variants — Fetches available sizes/colors for a product from Printful
  • POST /api/orders — Creates an order in Printful, then initiates a Stripe payment intent
  • POST /api/webhook — Stripe webhook handler for payment.intent.succeeded events
  • GET /api/health — Basic health check for monitoring

Data Flow: Customer selects a t-shirt variant on the frontend → frontend calls /api/variants to populate sizes → customer fills checkout form → /api/orders creates the Printful order and Stripe intent → Stripe redirects to success page → webhook confirms payment and triggers Printful fulfillment.

Infrastructure: AWS Setup

S3 Bucket Creation: We created a bucket named 86dfrom.com in us-east-1 (CloudFront requirement). The bucket is configured for static website hosting and locked down with a bucket policy that allows only CloudFront's origin access identity (OAI) to read objects. This keeps direct S3 URLs private and forces all traffic through the CDN.

aws s3api create-bucket \
  --bucket 86dfrom.com \
  --region us-east-1

ACM Certificates: We requested two certificates via AWS Certificate Manager:

  • 86dfrom.com — for the main domain (primary certificate)
  • 86from.com — for the typo-domain redirect (secondary certificate)

Both were validated via DNS CNAME records added to Route53. This approach is preferable to email validation for production sites because DNS validation is auditable and doesn't require manual email clicks.

CloudFront Distributions: We created two CloudFront distributions:

  • Distribution ID: E1A2B3C4D5E6F (example) — Primary distribution for 86dfrom.com, pointing to S3 bucket as origin, with cache behaviors for index.html (no cache) and assets (cache 1 year)
  • Distribution ID: E6F5D4C3B2A1 (example) — Redirect-only distribution for 86from.com, using a CloudFront Function to HTTP-301 redirect all requests to https://86dfrom.com

Route53 DNS: We added A records (alias) in the Route53 hosted zone for 86dfrom.com` pointing to the CloudFront distribution CNAME. This allows clients to resolve `86dfrom.com` → CloudFront edge → S3 origin.

Printful Integration

The Printful API key (valid until 2028) is stored in .env.local and used by the backend to:

  • Fetch the product catalog on server startup (route handler in app/api/products/route.js)
  • Look up variant details (sizes, colors, SKUs) dynamically in app/api/variants/route.js
  • Create orders via POST https://api.printful.com/orders when customers check out

We specifically target the Bella+Canvas 3001 t-shirt in black (variant IDs 4016–4020 for sizes XS–3XL). Variant IDs are hardcoded in the frontend because they're static; fetching all Printful products on every page load is wasteful. The scripts/get-printful-variants.js` script was used during development to discover these IDs and is kept in the repo for reference.

Stripe Payment Flow

Orders flow through Stripe test mode (for staging validation) or live mode (production). The /api/orders endpoint:

  1. Receives customer name, email, size, and quantity
  2. Creates a Printful order with these details (Printful assigns the order ID)
  3. Creates a Stripe PaymentIntent with amount = quantity × $25 (configurable)
  4. Returns the client secret to the frontend, which redirects to Stripe Checkout or Elements
  5. On successful payment, Stripe sends a webhook to `/api/webhook
  6. The webhook handler verifies the signature and updates order status in a database (or logs to Printful)

Webhook URL: Registered in the Stripe dashboard as https://86dfrom.com/api/webhook after Vercel deployment (we obtain the webhook secret from Stripe and add it to Vercel environment variables).

Deployment Strategy

Vercel: We deploy the Next.js application directly to Vercel using `npx vercel --prod`, which:

  • Builds the application with `next build`
  • Deploys to Vercel's global edge network
  • Automatically creates a production domain
  • Injects environment variables (Printful API key, Stripe keys, webhook secret)

S3 + CloudFront: Static assets (images, CSS bundles) are deployed to S3 via the scripts/deploy.sh script, which:

  • Syncs the site/ directory to the