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:

  1. Calls Printful API with the store token (from env var PRINTFUL_API_KEY)
  2. Fetches all products in the "86Store" Printful account
  3. Extracts variant IDs (e.g., Bella+Canvas 3001 in Black)
  4. 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:

  1. Local verification: npm run build to ensure all 5 routes compile cleanly
  2. Push to Vercel: npx vercel@latest --prod deploys the production version
  3. Set env vars: In Vercel dashboard → Settings → Environment Variables, paste Stripe and Printful keys
  4. Configure DNS: Point 86dfrom.com nameservers to Vercel or add CNAME/A records
  5. Create webhook endpoint: In Stripe dashboard → Webhooks, add endpoint URL https://86dfrom.com/api/webhook
  6. 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