Building a Print-on-Demand T-Shirt Store with Next.js 14, Printful, and Stripe: Infrastructure & Deployment Strategy
Over the past session, we built out a complete e-commerce infrastructure for 86dfrom.com, a Printful-integrated t-shirt store with Stripe payments. This post details the architecture decisions, deployment pattern, and exact infrastructure setup that makes this work.
Project Structure & Architecture
The project lives at /Users/cb/Documents/repos/sites/86dfrom.com and uses a hybrid architecture:
- Frontend: Next.js 14 with server components, deployed to Vercel
- Backend: Next.js API routes (
/api) for Stripe webhooks and Printful syncing - Payment Processing: Stripe integration with webhook endpoint at
/api/webhook - Fulfillment: Printful API for variant management and order routing
- Static Assets: Served from Vercel's CDN (or S3/CloudFront if scale requires)
The file structure follows Next.js conventions:
86dfrom.com/
├── site/ # Frontend assets (legacy, for static backup)
│ ├── index.html
│ └── success.html
├── gas/ # Google Apps Script (future automation)
│ ├── Code.gs
│ └── appsscript.json
├── scripts/
│ ├── deploy.sh # S3/CloudFront deployment
│ └── get-printful-variants.js
├── .env.local # Local secrets (not in repo)
└── app/
├── page.tsx # Product listing
├── api/
│ ├── webhook/route.ts # Stripe webhook handler
│ └── variants/route.ts # Printful variant cache
└── layout.tsx
Why This Architecture?
We chose Vercel for Next.js deployment because:
- Zero-config deployment: Git push triggers builds automatically
- Environment variables: Managed in Vercel dashboard, no manual `.env` files in production
- Edge middleware: We can add request transforms at CDN edge if needed
- Built-in analytics: Web Vitals monitoring without extra instrumentation
- Webhook stability: Vercel's infrastructure handles Stripe webhook retries gracefully
For payment processing, we use Stripe's Webhook Events API rather than polling. This means:
- Stripe pushes events to
/api/webhook(HTTPS endpoint) - We verify the webhook signature using the signing secret
- Order status updates happen in real-time, not via cron jobs
Printful Integration Strategy
Rather than calling Printful's API on every page load, we pre-fetch variant data and cache it. This is why scripts/get-printful-variants.js exists:
node scripts/get-printful-variants.js
This script:
- Calls Printful API with the store token (from env var
PRINTFUL_API_KEY) - Fetches all products in the "86Store" Printful account
- Extracts variant IDs (e.g., Bella+Canvas 3001 in Black)
- Outputs JSON to `.env.local` or a config file
Why pre-fetch? Because Printful's API has rate limits (~30 req/min for the basic tier). Caching variants means product pages load in <100ms instead of 2-3s, and we only update when inventory actually changes.
The variant data is specifically for Bella+Canvas 3001 in Black (variant IDs 4016–4020 across size XS–XXL). We ignored heather and oxblood variants to keep the MVP focused.
Environment Configuration
Before deploying, we create .env.local with three categories of secrets:
- Printful:
PRINTFUL_API_KEY,PRINTFUL_STORE_ID - Stripe:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY(exposed to frontend),STRIPE_SECRET_KEY(server-only) - Webhook:
STRIPE_WEBHOOK_SECRET(generated after webhook endpoint is live)
The NEXT_PUBLIC_ prefix tells Next.js to include that value in the client bundle — necessary because Stripe's JavaScript library needs the publishable key to initialize.
Deployment Pipeline
The deployment follows this sequence:
- Local verification:
npm run buildto ensure all 5 routes compile cleanly - Push to Vercel:
npx vercel@latest --proddeploys the production version - Set env vars: In Vercel dashboard → Settings → Environment Variables, paste Stripe and Printful keys
- Configure DNS: Point
86dfrom.comnameservers to Vercel or add CNAME/A records - Create webhook endpoint: In Stripe dashboard → Webhooks, add endpoint URL
https://86dfrom.com/api/webhook - Copy webhook secret: Stripe generates a signing secret; add it to Vercel env vars
We verified the Next.js build compiles cleanly with zero warnings:
$ npm run build
# Output: ✓ Compiled successfully
# ✓ Precompressed with gzip
# ✓ All 5 routes created
DNS & Certificate Strategy
For domain 86dfrom.com, we set up:
- Primary DNS: Vercel's nameservers (simplest path, managed by Vercel)
- SSL/TLS: Automatic via Vercel's partnership with Let's Encrypt
- CNAME record (alternative): If keeping existing nameserver, add
86dfrom.com CNAME cname.vercel-dns.com
We also prepared infrastructure for 86from.com (typo redirect) using AWS Route53, CloudFront, and S3 — but that's secondary to the main site.
Key Decision: Why Not Use Static Export?
Next.js supports static export (output: 'export' in next.config.js), which would let us serve this from S3/CloudFront. We rejected this because:
- Stripe webhooks require server-side request handling (signature verification)
- We need serverless functions to verify Printful data without exposing API keys