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 14with 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 Stripepayment_intent.succeededevents, 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.com→86dfrom.comdomain canonicalization - Route53 hosted zone:
86dfrom.comwith A-record alias to CloudFront distribution - ACM certificates: Public SSL/TLS for both
86dfrom.comand86from.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:
npm run build— Next.js compiles all 5 routes; zero errorsnpx vercel@latest --prod— Publishes API routes + environment variables to Vercelbash ./scripts/deploy.sh— Uploadssite/contents to S3 bucket86dfrom.com, invalidates CloudFront cache with path pattern/*- DNS propagates (A-record in Route53 already points to CloudFront distribution)
- 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:*withs3:GetObjectpermission 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