```html

Building a Headless Print-on-Demand Store: Next.js 14, Printful API, and AWS CloudFront Infrastructure

This post documents the technical architecture and deployment pipeline for 86dfrom.com, a serverless print-on-demand t-shirt storefront. We'll cover the full stack: Next.js 14 API routes for Printful integration, Stripe payment processing, S3 + CloudFront CDN infrastructure, and the deployment automation that ties it together.

What Was Built

A production-ready e-commerce site that:

  • Serves a static marketing site via CloudFront + S3 with sub-100ms latency
  • Integrates Printful's print fulfillment API to fetch live variant pricing and inventory
  • Processes Stripe payments with webhook verification
  • Deploys Next.js API routes to Vercel's serverless platform
  • Manages DNS and SSL/TLS via Route53 and AWS Certificate Manager

Architecture Overview

Frontend Stack:

  • Next.js 14 with React Server Components
  • Five routes: home, products, cart, checkout, success confirmation
  • Static HTML + CSS generated at build time for the marketing site
  • Client-side state management for cart operations

Backend Stack:

  • /api/printful-variants — fetches available product variants from Printful Store ID 5285935
  • /api/create-payment-intent — calls Stripe to create a PaymentIntent for the cart total
  • /api/webhook — listens for Stripe payment_intent.succeeded events, creates fulfillment orders in Printful

Infrastructure:

  • S3 bucket: 86dfrom.com (website content + versioned deployments)
  • CloudFront distribution: Primary CDN serving 86dfrom.com (distribution ID redacted for security)
  • CloudFront redirect function: Handles 86from.com86dfrom.com domain canonicalization
  • Route53 hosted zone: 86dfrom.com with A-record alias to CloudFront distribution
  • ACM certificates: Public SSL/TLS for both 86dfrom.com and 86from.com (DNS-validated)
  • Vercel project: Serverless API endpoints + environment variable management

Technical Decisions and Rationale

Why CloudFront + S3 for the Marketing Site?

The static HTML, CSS, and image assets don't need dynamic rendering per request. By pushing the site to S3 and fronting it with CloudFront, we achieve:

  • Cost: ~$0.085/GB transferred (CloudFront) vs. $0.20/GB for direct internet egress from EC2
  • Performance: 220+ edge locations globally; 86dfrom.com assets cached at AWS POPs near user
  • Simplicity: No server to manage, patch, or monitor. S3 bucket policy controls access.

The API routes run on Vercel's serverless platform, which auto-scales and allows us to keep secrets (Stripe SK, Printful API key) out of the CDN.

Why Printful Over Shopify or Custom Fulfillment?

Printful's API handles:

  • Real-time variant availability (size/color/fabric combinations)
  • Pricing lookup based on quantity
  • Order fulfillment submission (no manual step)
  • Shipping cost estimation

This lets us build a lightweight custom storefront without maintaining inventory or warehousing. The Printful Store ID 5285935 (registered in dangerouscentaur's account) owns the product catalog and processes print-and-ship.

Why Stripe Webhooks?

Synchronous payment confirmation risks race conditions (user closes browser before order is created). By listening for payment_intent.succeeded webhooks, we:

  • Guarantee the order reaches Printful even if the client disconnects
  • Decouple payment processing from order creation (async pattern)
  • Have a durable, signed event record from Stripe (webhook signature verification in /api/webhook)

File Structure and Deployment Flow

~/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html          # Homepage
│   ├── products.html       # Product listing
│   ├── cart.html           # Shopping cart
│   ├── checkout.html       # Stripe Elements form
│   └── success.html        # Order confirmation
├── gas/
│   ├── Code.gs             # Google Apps Script (unused in this deploy)
│   ├── appsscript.json     # GAS manifest
│   └── .clasp.json         # clasp project config
├── scripts/
│   └── deploy.sh           # S3 upload + CloudFront invalidation
├── .env.local              # Printful API key, Stripe SK, Store ID
└── .env.example            # Template (safe to commit)

Deployment Sequence:

  1. npm run build — Next.js compiles all 5 routes; zero errors
  2. npx vercel@latest --prod — Publishes API routes + environment variables to Vercel
  3. bash ./scripts/deploy.sh — Uploads site/ contents to S3 bucket 86dfrom.com, invalidates CloudFront cache with path pattern /*
  4. DNS propagates (A-record in Route53 already points to CloudFront distribution)
  5. TLS certificate validation completes (ACM validates domain ownership via Route53 CNAME records)

Key Infrastructure Details

S3 Bucket Configuration:

  • Bucket name: 86dfrom.com
  • Block all public access: false (allow CloudFront to read via bucket policy)
  • Bucket policy: Principal AWS:cloudfront:* with s3:GetObject permission on *
  • Versioning: Enabled (rollback capability if a deploy corrupts assets)
  • Default index: index.html (CloudFront behavior configured to serve this for /)

CloudFront Distribution:

  • Origin: S3 bucket 86dfrom.com.s3.amazonaws.com (via origin access