Building a Print-on-Demand T-Shirt Storefront: Integrating Printful, Stripe, and Next.js 14 on AWS CloudFront
Over the course of a development session, we built out a full e-commerce storefront for 86dfrom.com, a print-on-demand t-shirt site powered by Printful's API, Stripe payment processing, and Next.js 14. This post details the infrastructure decisions, deployment architecture, and integration patterns that brought the site from local development to production-ready.
Project Overview and Architecture
The 86dfrom.com project is a Next.js 14 application structured to serve both a client-facing storefront and a backend API for payment and fulfillment webhooks. The tech stack includes:
- Frontend: Next.js 14 with static HTML fallback for CloudFront
- Backend: API routes for Stripe webhooks and Printful variant lookups
- Payment Processor: Stripe (test and live keys)
- Print Fulfillment: Printful API for product variants and order routing
- Infrastructure: AWS S3, CloudFront, ACM, Route53, and Vercel
- Automation: Google Apps Script for potential order management workflows
The project was organized into three primary directories:
/site— HTML and static assets (index.html, success.html)/gas— Google Apps Script code (Code.gs, appsscript.json) for extensibility/scripts— Deployment and utility scripts (deploy.sh, get-printful-variants.js)
Multi-Region Deployment: Desktop Development → Repository → Production
A key architectural decision was establishing a clear separation between local development, version control, and production deployment. Files were initially created in /Users/cb/Desktop/86dfrom, then mirrored to ~/Documents/repos/sites/86dfrom.com for proper version control and CI/CD integration.
The deployment pipeline follows this flow:
Local Dev (/Desktop/86dfrom)
↓
Git Repository (~/Documents/repos/sites/86dfrom.com)
↓
Vercel (Next.js production build)
↓
S3 + CloudFront (static fallback and redirects)
↓
Route53 DNS (86dfrom.com and 86from.com)
This redundancy serves a purpose: Vercel handles the dynamic API routes (webhooks, variant lookups), while S3/CloudFront acts as a fallback for static content and handles domain redirects for typos (86from.com → 86dfrom.com).
Infrastructure Components and DNS Configuration
AWS S3 Buckets: We created dedicated S3 buckets for static site hosting and CDN caching. The bucket naming convention follows the domain: 86dfrom.com (primary) and redirect buckets for common typos. Bucket policies were configured to allow CloudFront distributions to read objects while denying direct public access.
CloudFront Distributions: Two distributions were created:
- Primary Distribution (86dfrom.com): Serves the site content from S3 with caching headers optimized for HTML and asset files. Cache invalidation is triggered after each deployment using
/*wildcard paths. - Redirect Distribution (86from.com): Uses CloudFront Functions to redirect typos and alternate domain spellings to the primary domain. The function executes at the edge, ensuring instant redirects without backend involvement.
ACM Certificates: We requested two SSL/TLS certificates from AWS Certificate Manager:
- Certificate for
86dfrom.comwith DNS validation - Certificate for
86from.comwith DNS validation
DNS validation records (CNAME entries) were added to Route53 to prove domain ownership. The validation typically completes within minutes, after which the certificates are active for CloudFront.
Route53 Hosted Zone: The 86dfrom.com hosted zone was created in Route53 to manage all DNS records. A-records point to CloudFront distribution aliases, eliminating the need for CNAME records and avoiding the "naked domain" problem. Both the primary domain and www subdomain are configured as alias records pointing to their respective CloudFront distributions.
Environment Configuration and Secrets Management
The project uses a .env.local file to store sensitive configuration values, populated during the deployment phase. The environment variables include:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY— Public Stripe key for client-side token generationSTRIPE_SECRET_KEY— Secret Stripe key for server-side transactions (test or live)PRINTFUL_API_KEY— Printful API token for variant lookups and order creationPRINTFUL_STORE_ID— Store identifier within the Printful account (86Store)STRIPE_WEBHOOK_SECRET— Webhook signing secret for validating Stripe events (configured after deployment)
The approach of separating public keys (prefixed with NEXT_PUBLIC_) from secret keys ensures that only necessary data is bundled into the client-side JavaScript, while secrets remain server-side only.
Printful Integration and Variant Management
The Printful API integration centers on retrieving product variant IDs, which are stored in scripts/get-printful-variants.js. This script queries the Printful API for Bella+Canvas 3001 t-shirts in black, extracting the size/color variant combinations needed for the storefront dropdown.
The script outputs variant IDs (e.g., 4016–4020 for sizes XS–XL), which are hardcoded into the environment or frontend configuration. This approach avoids runtime API calls for variant lookup, reducing latency and Printful rate-limit exposure on the production storefront.
Why this pattern? Variant data is stable and rarely changes. Baking it into the app at deploy time is simpler and faster than querying Printful on every product page load. If variants change, a re-deploy updates the storefront.
Stripe Webhook Configuration
The API route /api/webhook (created at pages/api/webhook.ts in Next.js) listens for Stripe events. The route validates incoming webhooks using the STRIPE_WEBHOOK_SECRET, ensuring that requests originate from Stripe's servers.
Webhook handling includes:
payment_intent.succeeded— Customer paid; trigger Printful order creationpayment_intent.payment_failed— Log failed payment for customer follow-upcharge.refunded— Cancel corresponding Printful order if applicable
The webhook secret is obtained from Stripe's dashboard after deploying to production and configuring the webhook URL at https://86dfrom.com/api/webhook. This creates a feedback loop: Stripe → Vercel API → Printful order creation → fulf