Building a Print-on-Demand T-Shirt Store with Next.js 14, Printful, and Stripe: Infrastructure & Deployment
This post documents the infrastructure and deployment pipeline for 86dfrom.com, a print-on-demand t-shirt storefront built on Next.js 14, integrated with Printful's API for inventory management and Stripe for payment processing. We'll cover the full stack: from local development setup through AWS S3/CloudFront static hosting, Vercel serverless functions, and multi-region DNS configuration.
Architecture Overview
The project uses a hybrid hosting model:
- Next.js 14 application (local dev at
/Users/cb/Documents/repos/sites/86dfrom.com) with five API routes and static pages - Vercel production deployment for serverless functions (
/api/webhook,/api/variants, etc.) - AWS S3 + CloudFront for static asset delivery and domain redirect (both
86dfrom.comand86from.comvariants) - Route53 for DNS management across dangerouscentaur properties
- Stripe for payment processing with webhook validation
- Printful API for real-time variant inventory and order fulfillment
Project Structure
The repository is organized as follows:
~/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html # Primary landing/product page
│ └── success.html # Post-purchase confirmation
├── gas/
│ ├── Code.gs # Google Apps Script (Google Sheets integration)
│ ├── appsscript.json # GAS manifest
│ └── .clasp.json # Clasp configuration (project ID)
├── scripts/
│ ├── deploy.sh # S3 + CloudFront deployment
│ └── get-printful-variants.js # Fetch variant IDs from Printful
├── .env.local # Local environment variables (populated after credential setup)
└── .env.example # Template with required variables
The Next.js application lives in the parent project at /Users/cb/Desktop/86dfrom during active development, with verified clean builds confirming all five routes compile without errors.
Static Hosting: S3 + CloudFront Architecture
Static assets are deployed to AWS S3 with CloudFront caching and Route53 DNS pointing. Two separate distributions were created to handle domain variations:
Primary Distribution (86dfrom.com)
- S3 bucket:
86dfrom.com(created during session) - CloudFront distribution: Configured with S3 origin, cache behaviors for
.html,.css,.js, and image assets - ACM certificate: Requested for
86dfrom.comwith DNS validation via Route53 - Route53 alias record: Points
86dfrom.comto CloudFront distribution DNS name
Redirect Distribution (86from.com)
A second CloudFront distribution was created specifically for the 86from.com typo variant to redirect all traffic to the primary domain. This required:
- Separate S3 bucket for redirect distribution
- CloudFront function (Lambda@Edge equivalent) to inject HTTP redirect responses
- Route53 CNAME alias for
86from.compointing to the redirect CloudFront distribution
The redirect function reads the incoming Host header and returns a 301 permanent redirect to https://86dfrom.com, preserving any query parameters.
Environment Configuration & Secrets Management
The project requires several environment variables for local development and Vercel deployment:
# .env.local (created after credential gathering)
PRINTFUL_API_KEY=
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=pk_live_... # or pk_test_...
DC_STRIPE_PUBLISHABLE_KEY=
NEXT_PUBLIC_STRIPE_KEY=
The .env.local file is excluded from version control. A template file .env.example documents all required variables without exposing actual secrets. Secrets are stored in a private shared file outside the repository for team access.
Key decision: Test mode Stripe keys can be used for development and staging, but production payment processing requires live keys. The webhook secret (whsec_...`) is obtained from Stripe dashboard after Vercel deployment is live and a webhook endpoint is configured.
Printful API Integration
The Printful account "Hello Dangerous" has a store called "86Store" with API access for all scopes. To populate variant IDs:
# Run locally to fetch variant metadata
node ~/Documents/repos/sites/86dfrom.com/scripts/get-printful-variants.js
This script calls Printful's REST API (base URL: https://api.printful.com) to retrieve all product variants for the Bella+Canvas 3001 Black t-shirt. The variant IDs (specifically 4016–4020 for plain Black colorway) are hardcoded into the site's product data structure, bypassing the need for dynamic inventory queries on every pageload.
Why hardcoded? The t-shirt catalog is static—only the inventory count changes. Fetching variant metadata once and caching it reduces API calls and improves page load time. Inventory sync can happen via a scheduled background job or on-demand via the webhook.
Google Apps Script for Order Logging
The gas/ directory contains a Google Apps Script deployment that logs orders to a Google Sheet. This is managed via the Clasp CLI:
gas/Code.gscontains the webhook handler that receives order data from Stripe/Printfulgas/appsscript.jsondefines the GAS manifest with library dependencies and version infogas/.clasp.jsonstores the GAS project ID for Clasp authentication
The GAS script is deployed separately from the Next.js app and receives webhook POST requests at its published URL, appending order details to a Google Sheet for manual order tracking.
Deployment Pipeline
Local to production involves three distinct deployments:
1. Next.js to Vercel
# From /Users/cb/Desktop/86dfrom (Next.js app root)
npx vercel@latest --prod
This deploys serverless functions to Vercel's infrastructure. After deployment, environment variables must be added to the Vercel project settings (not in .env.local), so they're available in the production environment.
2. Static Assets to S3 + CloudFront Invalidation
# From ~/Documents/repos/sites/86dfrom.com
bash scripts