Building a Printful-Integrated T-Shirt Store on Next.js 14: Infrastructure Setup for 86dfrom.com

This post documents the full-stack setup of a print-on-demand t-shirt e-commerce site built on Next.js 14, integrated with Printful for fulfillment and Stripe for payments. We'll cover the architecture decisions, infrastructure provisioning, and deployment pipeline that enables seamless product variant management and order processing.

Project Overview & Architecture

86dfrom.com is a Printful-backed t-shirt store where customers can purchase custom apparel with dynamic pricing and inventory sync. The stack consists of:

  • Frontend: Next.js 14 with App Router, static HTML fallback for the storefront
  • API Layer: Next.js API routes for Printful sync and Stripe webhook handling
  • CDN: CloudFront with S3 origin for static asset delivery
  • DNS: Route53 for domain management and traffic routing
  • Payment: Stripe for PCI-compliant payment processing
  • Fulfillment: Printful API for variant data, order placement, and tracking

The architecture prioritizes separation of concerns: static content via CloudFront/S3, dynamic API routes via Vercel, and a flexible webhook endpoint for asynchronous order updates.

Build Configuration & Verification

Before infrastructure deployment, we confirmed the Next.js build compiles cleanly with all five API routes:


/api/route.ts              // Health check
/api/variants              // Fetch Printful variant data
/api/orders                // Order creation endpoint
/api/webhook               // Stripe webhook listener
/api/callback              // Printful callback handler

The next build command executed without errors, confirming no circular dependencies, missing imports, or TypeScript issues before touching infrastructure.

Environment Configuration & Secrets Management

The project uses a three-tier secrets approach:

  • .env.local (Git-ignored): Local development secrets
  • Vercel Environment Variables: Production secrets injected at build/deploy time
  • AWS Secrets Manager (optional): For rotation and audit logging

Required environment variables for 86dfrom.com:


NEXT_PUBLIC_STORE_NAME=86Store
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
PRINTFUL_STORE_ID=...

The NEXT_PUBLIC_* prefix ensures client-side availability without exposing secrets; the Stripe and Printful keys remain server-only. The webhook secret is provisioned after the Vercel deployment endpoint is live.

Printful Variant Data Pipeline

Product variants (sizes, colors, SKUs) are fetched from Printful's API and cached. The workflow:

  1. Script scripts/get-printful-variants.js calls Printful's /products/{product_id} endpoint
  2. Extracts variant IDs for the Black Bella+Canvas 3001 blank (items 4016–4020)
  3. Writes variant mappings to site/variants.json for client-side rendering
  4. API route /api/variants serves this cached data without re-querying Printful on every pageload

This caching strategy reduces API calls to Printful (rate-limited at 60 req/min for standard accounts) and keeps product pages fast. The variant JSON structure includes:


{
  "size": "L",
  "color": "Black",
  "printful_variant_id": 4018,
  "sku": "86DFROM-BK-L",
  "price": 2499  // in cents for Stripe
}

DNS & CDN Infrastructure

86dfrom.com uses a hybrid CDN approach:

  • S3 Bucket: 86dfrom-com-site (us-east-1) stores static assets (CSS, JS, fonts)
  • CloudFront Distribution: Caches S3 objects with 1-hour TTL for media, 24-hour for code
  • Route53 Hosted Zone: 86dfrom.com with CNAME record pointing to CloudFront
  • ACM Certificate: TLS 1.2+ for HTTPS, auto-renewed by AWS

The S3 bucket policy restricts access to CloudFront only (Origin Access Identity), preventing direct S3 URL leakage. The CloudFront distribution includes a Lambda@Edge function for conditional redirects (e.g., 86from.com86dfrom.com).

Why this setup? S3 + CloudFront is significantly cheaper than hosting on Vercel for large media files, and the Origin Access Identity ensures users can't bypass the cache by guessing S3 URLs. The Route53 health checks can trigger failover if the origin becomes unhealthy.

Vercel Deployment Configuration

The Next.js app is deployed to Vercel with:


npx vercel@latest --prod --env-file .env.local

Vercel's build output:

  • Static routes (index, success pages) → Edge Cache (global CDN)
  • API routes → Serverless Functions (auto-scaled)
  • ISR routes (optional) → Revalidation at request time

All environment variables are injected into the Vercel project's settings, ensuring they're available during build and runtime without committing them to Git.

Stripe Integration & Webhook Setup

The webhook endpoint at /api/webhook listens for Stripe events:

  • payment_intent.succeeded → Place order in Printful
  • payment_intent.payment_failed → Log failure, notify user
  • charge.refunded → Cancel order in Printful

After deploying to Vercel, the webhook URL is registered in Stripe Dashboard → Developers → Webhooks with the signing secret returned by Stripe. The webhook secret is then added to Vercel's environment variables.

The webhook handler verifies the request signature using stripe.webhooks.constructEvent(body, signature, secret) before processing. This prevents spoofed webhook calls.

Deployment Script & Automation

The deployment script scripts/deploy.sh orchestrates the full pipeline:


#!/bin/bash
# 1. Build Next.js
npm run build

# 2. Export static routes to site/
npm run export

# 3. Sync site/ to S3
aws s3 sync site/ s3://86dfrom-com-site/

# 4. Invalidate CloudFront cache
aws cloudfront create-