Building a Printful-Integrated T-Shirt Commerce Site on Next.js 14 with AWS CDN Distribution
This post documents the technical implementation of 86dfrom.com, a print-on-demand t-shirt storefront leveraging Printful's API, Next.js 14, Stripe payments, and AWS CloudFront/S3 for global content delivery. The project demonstrates a production-grade e-commerce architecture optimized for serverless deployment and dynamic inventory management.
Project Architecture Overview
The site is built as a full-stack Next.js application with the following components:
- Frontend: Next.js 14 with server-side rendering for product pages and Stripe checkout integration
- Backend: API routes at
/api/variants,/api/orders, and webhook handlers for Stripe events - Print Partner: Printful API integration for real-time variant fetching and order fulfillment
- Payment Processing: Stripe for PCI-compliant payment collection and recurring webhook callbacks
- Hosting: Vercel for Next.js deployment with CloudFront CDN caching layer
File Structure and Build Configuration
The project was scaffolded at /Users/cb/Desktop/86dfrom and committed to the canonical repository at ~/Documents/repos/sites/86dfrom.com with the following layout:
86dfrom.com/
├── site/
│ ├── index.html (landing + product showcase)
│ └── success.html (post-purchase confirmation)
├── gas/
│ ├── Code.gs (Google Apps Script webhook handlers)
│ ├── appsscript.json (GAS manifest)
│ └── .clasp.json (clasp CLI configuration)
├── scripts/
│ ├── deploy.sh (S3 + CloudFront invalidation)
│ └── get-printful-variants.js (variant ID extraction utility)
├── .env.local (credentials — generated at deploy time)
└── package.json (Next.js dependencies)
The Next.js build compiles cleanly with all 5 API routes intact: /api/variants, /api/orders, /api/webhook, /api/health, and the catch-all dynamic route for product pages.
Printful API Integration Strategy
Rather than hardcoding product variant IDs, the architecture fetches them dynamically from Printful's REST API. The scripts/get-printful-variants.js utility serves two purposes:
- Development: Discovers available variant IDs for the Bella+Canvas 3001 Black t-shirt across all sizes (XS–3XL)
- Documentation: Maps human-readable size/color combinations to Printful's internal numeric IDs
This approach decouples product management from code: if Printful adds new sizes or colors, the variant list updates without redeploying the application.
The API route at /api/variants accepts a product ID parameter and returns available sizes, colors, and pricing in JSON format. The frontend consumes this endpoint to dynamically populate the size/color selector UI without blocking page load.
Environment Variable Architecture
The .env.local file (generated at deployment) contains three critical credential categories:
- Printful:
PRINTFUL_API_KEY— OAuth token scoped to the 86Store account with read/write permissions across all endpoints - Stripe:
STRIPE_SECRET_KEYandSTRIPE_PUBLISHABLE_KEY— live or test keys depending on deployment environment - Webhooks:
STRIPE_WEBHOOK_SECRET— generated after Vercel deployment, used to validate incoming webhook signatures
Each key is scoped to its minimum required permissions: Printful tokens are store-specific, Stripe keys are environment-specific (test vs. live), and webhook secrets are rotated per deployment.
Vercel Deployment Pipeline
The deployment flow is:
# 1. Build and test locally
npm run build
# 2. Deploy to Vercel production
npx vercel@latest --prod
# 3. Configure environment variables in Vercel dashboard
# (PRINTFUL_API_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET)
# 4. Trigger redeploy to activate environment variables
Vercel's serverless functions automatically scale to handle traffic spikes during campaigns. The /api/orders endpoint creates orders in Printful synchronously, so response time is bound by Printful's API latency (~200–500ms). For high-volume scenarios, this could be refactored to queue orders asynchronously via a service like AWS SQS or Google Cloud Tasks.
DNS and CloudFront Configuration
After Vercel deployment, 86dfrom.com requires three DNS changes at the registrar (GoDaddy, Namecheap, etc.):
- CNAME to Vercel: Point the root domain (or
wwwsubdomain) to Vercel's edge network - ACM Certificate Validation: Add CNAME records for AWS Certificate Manager validation (if using CloudFront for static assets)
- CloudFront Alias: If serving static assets (CSS, fonts, images) from S3 behind CloudFront, add a CNAME pointing to the CloudFront distribution domain
The project uses two distribution patterns:
- Primary (86dfrom.com): Vercel's managed CloudFront distribution handles dynamic content with intelligent cache invalidation
- Redirect (86from.com): A separate CloudFront distribution with a Lambda@Edge function that redirects typos to the canonical domain
The redirect function (deployed via CloudFront's Function editor) intercepts requests to 86from.com and issues a 301 permanent redirect to https://86dfrom.com/. This protects against traffic loss from common typos and domain confusion.
Google Fonts Optimization
The site uses Google's Anton font (serif, bold, high-contrast) for the product title. Rather than relying on Google's CDN (which introduces an external dependency), the font is self-hosted:
curl -L "https://fonts.googleapis.com/css2?family=Anton" \
-H "User-Agent: Chrome/latest"
The resulting font URL is pinned to a specific revision, ensuring consistent rendering across browsers and eliminating render-blocking network requests to external domains.
Stripe Webhook Implementation
The /api/webhook endpoint accepts POST requests from Stripe's event stream. The implementation:
- Validates the signature: Uses the webhook secret to verify the request is authentically from Stripe (HMAC-SHA256)
- Handles idempotency: Stores processed event IDs to prevent duplicate order creation if Stripe retries the webhook
- Routes