```html

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

This post documents the complete technical setup for 86dfrom.com, a print-on-demand (POD) t-shirt storefront built on a modern serverless stack. We'll walk through the architecture decisions, infrastructure provisioning on AWS/CloudFront, and the integration patterns that connect a Next.js frontend to Printful's catalog and Stripe's payment processing.

Project Overview & Architecture

The 86dfrom project is a Next.js 14 application designed to:

  • Display a curated t-shirt product catalog (sourcing designs and variants from Printful)
  • Accept orders via Stripe payment forms
  • Route orders to Printful for print, pack, and ship
  • Handle webhooks from both Printful (order status) and Stripe (payment events)

The application lives at /Users/cb/Documents/repos/sites/86dfrom.com with the following structure:

86dfrom.com/
├── site/                  # Static HTML & client assets
│   ├── index.html        # Main product page
│   └── success.html      # Post-purchase confirmation
├── gas/                  # Google Apps Script (optional backup)
│   ├── Code.gs
│   └── appsscript.json
└── scripts/
    ├── deploy.sh         # S3 + CloudFront deployment
    └── get-printful-variants.js  # Variant ID fetcher

The Next.js application itself compiles cleanly with all 5 core routes (index, product, checkout, webhook handlers for Printful and Stripe) without errors.

Infrastructure Setup on AWS

S3 & CloudFront Distribution

Following the pattern established by sibling projects in the dangerouscentaur ecosystem (e.g., chuckladd.com), we provisioned:

  • S3 Bucket: 86dfrom.com in us-east-1 (required for CloudFront origin)
  • Bucket Policy: Restricts direct public access; all traffic flows through CloudFront distribution
  • CloudFront Distribution: Caches static assets (HTML, CSS, JS, images) at edge locations globally
  • ACM Certificate: Issued for both 86dfrom.com and www.86dfrom.com with DNS validation via Route53

Why this approach? CloudFront provides:

  • Global edge caching, reducing latency for users worldwide
  • DDoS protection via AWS Shield Standard
  • SSL/TLS enforcement at the edge
  • Lambda@Edge capability for future custom request/response logic (e.g., redirects, header injection)

The S3 bucket itself is not publicly accessible; the bucket policy restricts GET/HEAD operations to the CloudFront Origin Access Identity (OAI), preventing direct S3 URL leakage.

DNS & Route53 Configuration

Both 86dfrom.com and the redirect domain 86from.com (note: missing 'd') are managed in Route53:

  • 86dfrom.com: A-record (alias) pointing to the primary CloudFront distribution
  • 86from.com: A-record pointing to a separate CloudFront distribution configured with a CloudFront Function that redirects all traffic to https://86dfrom.com

Why a separate redirect distribution? CloudFront Functions (not Lambda@Edge, which adds cold-start latency) are ideal for simple HTTP redirects. They execute in microseconds at the edge, providing instant 301 redirects before the user's browser ever hits the origin S3 bucket.

The redirect function is deployed as:

// CloudFront Function (86from.com → 86dfrom.com)
function handler(event) {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: {
      'location': { value: 'https://86dfrom.com' + event.request.uri }
    }
  };
}

Printful Integration

Printful serves as the print-on-demand fulfillment partner. The integration requires:

  1. API Credentials: A store-level API token (generated via printful.com → Dashboard → Store Settings → API) with full scope access
  2. Variant Mapping: Script at scripts/get-printful-variants.js fetches available product variants from Printful and extracts variant IDs
  3. Product Data: The store carries Bella+Canvas 3001 black t-shirts in sizes XS–3XL. Variant IDs are hardcoded in the checkout flow to ensure correct SKU mapping when orders are placed

The variant-fetching script makes authenticated GET requests to the Printful API (e.g., https://api.printful.com/products) and extracts the relevant IDs for the product you want to sell. These IDs are then embedded in .env.local so the checkout route knows exactly which Printful variant ID corresponds to each size/color combination.

Stripe Payment Processing

Payment flows through Stripe, with two key credentials required:

  • Publishable Key: Embedded in the client-side form (can be exposed, used for Stripe.js initialization)
  • Secret Key: Stored server-side in .env.local, used for token creation, charge processing, and webhook verification

API Routes (Next.js):

  • /api/create-payment-intent — Creates a Stripe PaymentIntent server-side, returns client secret to frontend
  • /api/webhook — Receives and verifies Stripe webhook signatures, processes payment_intent.succeeded events

The webhook secret (format: whsec_...) is obtained after Vercel deployment and webhook endpoint registration at dashboard.stripe.com → Developers → Webhooks. This secret is used to verify that incoming webhook payloads are genuinely from Stripe, not forged requests.

Environment Configuration & Secrets Management

The project uses a .env.local file (git-ignored) to store credentials. Template:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
PRINTFUL_STORE_ID=...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020

Additionally, Vercel's environment variable UI is used to persist these secrets in production, ensuring the deployed application has access to credentials without committing them to the repository.

Deployment Pipeline

To S