```html

Building a Print-on-Demand T-Shirt Store with Next.js 14, Printful, and Stripe: Infrastructure & Deployment

This post documents the technical architecture and deployment strategy for 86dfrom.com, a headless print-on-demand (POD) t-shirt storefront built on Next.js 14 with Printful API integration and Stripe payment processing. We'll cover the full stack: local development setup, API route architecture, AWS infrastructure decisions, and the deployment pipeline.

Project Structure & Technology Stack

The application is organized as a Next.js 14 project with the following directory structure:

~/Documents/repos/sites/86dfrom.com/
├── site/                    # Frontend static assets & HTML
│   ├── index.html          # Main product landing page
│   └── success.html        # Post-purchase success page
├── gas/                    # Google Apps Script backend
│   ├── Code.gs             # Webhook handlers & form processing
│   └── appsscript.json     # GAS manifest (version control)
├── scripts/                # Deployment utilities
│   └── deploy.sh           # S3/CloudFront deployment script
├── .env.local              # Local environment variables
└── .clasp.json             # clasp CLI config for GAS deployment

The core application uses:

  • Next.js 14 with App Router for API routes and SSR
  • Printful API v2 for product variant management and order creation
  • Stripe API for payment processing and webhook validation
  • Google Apps Script for webhook handling (alternative serverless compute)
  • AWS S3 + CloudFront for static site hosting and edge caching

API Architecture: Five Core Routes

The Next.js application implements five API routes that handle the full purchase flow:

  • /api/variants — Fetches available Printful product variants (color/size options) and caches them
  • /api/calculate — Computes print costs via Printful API based on selected variant
  • /api/create-payment-intent — Initializes a Stripe PaymentIntent for client-side checkout
  • /api/confirm-order — Finalizes the order after Stripe payment confirmation and creates fulfillment on Printful
  • /api/webhook — Receives and validates Stripe webhook events (payment success, failures)

Each route is implemented as a separate file in the app/api/ directory following Next.js conventions. The build process was verified clean with all five routes compiling without errors.

Printful Integration Strategy

Printful serves as the fulfillment provider. Rather than hardcoding variant IDs, we populate them dynamically from the Printful API:

Step 1: Fetch Variant IDs

A utility script (scripts/get-printful-variants.js) queries the Printful API to retrieve available product variants for the Bella+Canvas 3001 in black:

node scripts/get-printful-variants.js

This script hits the Printful endpoint for product 94 (Bella+Canvas 3001) and extracts variant IDs for sizes XS–3XL. The response is structured as:

{
  "variant_id": 4016,  // Black XS
  "size": "XS",
  "color": "Black"
}

We identified five black variants (IDs: 4016–4020) from the response and stored them in environment variables.

Step 2: Environment Configuration

The .env.local file stores Printful and Stripe credentials:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_SECRET_KEY=sk_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
NEXT_PUBLIC_PRINTFUL_STORE_ID=...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020

Note the NEXT_PUBLIC_ prefix for browser-accessible keys (Stripe publishable key only—never expose secret keys).

AWS Infrastructure: S3 + CloudFront + Route53

For production hosting, we configured a tiered AWS setup:

S3 Bucket Configuration

Created bucket 86dfrom.com with:

  • Static website hosting enabled (serves index.html on root requests)
  • Versioning enabled for rollback capability
  • Public read access via bucket policy (restricted to CloudFront distribution only)
  • No CORS configuration needed (CloudFront handles origin requests)

CloudFront Distribution

Created distribution d.86dfrom.com with:

  • Origin: S3 bucket website endpoint (86dfrom.com.s3-website-us-west-2.amazonaws.com)
  • Origin Access Control: Restricted via OAC policy (bucket only accessible from CloudFront)
  • Caching: 24-hour TTL for HTML; 365-day TTL for immutable assets (JS, CSS, fonts)
  • SSL Certificate: ACM certificate for 86dfrom.com (auto-renewal enabled)
  • Default Root Object: index.html
  • Error Handling: 404 errors served as index.html (SPA routing fallback)

Route53 DNS Configuration

Added DNS records in the hosted zone:

  • 86dfrom.com → A record → CloudFront distribution
  • www.86dfrom.com → CNAME → CloudFront distribution (redirect)
  • ACM validation records (auto-created, temporary)

Redirect Domain (86from.com → 86dfrom.com)

For the typo domain, created a separate CloudFront distribution with a CloudFront Function that issues a 301 redirect:

function handler(event) {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: {
      'location': { value: 'https://86dfrom.com' }
    }
  };
}

This function is attached to the viewer-request event of the redirect distribution, eliminating the need for S3 redirect rules.

Deployment Pipeline

Local to Vercel (Next.js Routes)

The API routes are deployed via Vercel:

npm run build    # Verify Next.js compilation
npx vercel@latest --prod  # Deploy