Building a Printful + Stripe T-Shirt E-Commerce Site on Next.js 14: Infrastructure, Deployment, and API Integration

This post documents the full build and deployment of 86dfrom.com, a Next.js 14 t-shirt storefront powered by Printful print-on-demand and Stripe payments. We cover architecture decisions, infrastructure provisioning on AWS (S3 + CloudFront + Route53), environment configuration, and API integration patterns.

Project Overview

The goal was to create a lightweight, serverless e-commerce site that:

  • Displays t-shirt variants fetched from Printful's API at build time
  • Accepts Stripe payments via a dedicated webhook endpoint
  • Deploys to Vercel for the application tier
  • Serves static assets from S3 + CloudFront for geographic distribution
  • Maintains DNS records in Route53 for unified AWS management

The full source lives in /Users/cb/Documents/repos/sites/86dfrom.com/ with three key directories:

  • site/ — Next.js app (HTML, CSS, client-side code)
  • gas/ — Google Apps Script bindings for form submission (legacy fallback)
  • scripts/ — Deployment and API utilities

Architecture & Technology Stack

Application Tier: Next.js 14 with App Router, deployed to Vercel. The build compiles cleanly across five routes:

  • / — Homepage with product grid
  • /checkout — Cart and Stripe Checkout session handler
  • /api/webhook — Stripe webhook receiver for payment confirmation
  • /api/variants — Dynamic variant metadata endpoint
  • /success — Post-payment confirmation page

Print Fulfillment: Printful API integration fetches live product variants at build time. We store variant IDs in environment variables rather than hardcoding, enabling easy SKU swaps without code changes. The build process validates all variant IDs against Printful's catalog before deploying.

Payment Processing: Stripe's Payment Intents API handles all transactions. The webhook endpoint at /api/webhook verifies signatures using the webhook secret and updates order status in real time. This decouples the checkout UI from payment state, improving resilience.

CDN & Static Hosting: CloudFront distribution (d1234567890ab.cloudfront.net, exact ID redacted) serves the site globally. S3 bucket 86dfrom-com-static holds origin content. Route53 health checks and alias records ensure failover and DNS resolution at sub-second latency.

Infrastructure Provisioning

S3 Bucket Creation:

# Bucket name matches domain for clarity and AWS compliance
aws s3 mb s3://86dfrom-com-static \
  --region us-east-1 \
  --create-bucket-configuration LocationConstraint=us-east-1

We chose us-east-1 because CloudFront requires certificates and bucket origins in that region. The bucket name includes the domain to avoid collisions across environments and projects.

SSL/TLS Certificate (ACM):

aws acm request-certificate \
  --domain-name 86dfrom.com \
  --subject-alternative-names www.86dfrom.com \
  --validation-method DNS \
  --region us-east-1

ACM's DNS validation prevents issuing certificates to domains we don't control. We added the validation CNAME records to Route53, waited for certificate issuance (typically 5–10 minutes), then verified status:

aws acm describe-certificate \
  --certificate-arn arn:aws:acm:us-east-1:ACCOUNT_ID:certificate/UUID \
  --region us-east-1

CloudFront Distribution:

The distribution was created via AWS console (CLI is verbose for this). Key settings:

  • Origin: 86dfrom-com-static.s3.us-east-1.amazonaws.com with Origin Access Identity (OAI) to prevent direct bucket access
  • Default Root Object: index.html
  • Viewer Protocol Policy: Redirect HTTP to HTTPS
  • Cache Behaviors: API routes (/api/*) bypass cache; static assets cache for 1 year with versioned filenames
  • Domain Aliases: 86dfrom.com and www.86dfrom.com
  • SSL Certificate: The ACM certificate from above
  • CloudFront Functions: A redirect function (deployed as 86dfrom-redirect) enforces HTTPS and www normalization

The OAI policy restricts S3 access to CloudFront only, preventing accidental public reads. Deployment invalidates the CloudFront cache after uploading to S3:

aws cloudfront create-invalidation \
  --distribution-id DISTRIBUTION_ID \
  --paths "/*"

Route53 DNS Records:

We added two alias records in the 86dfrom.com hosted zone:

  • 86dfrom.com → CloudFront distribution (alias)
  • www.86dfrom.com → CloudFront distribution (alias)

Route53 alias records are AWS-specific and incur no query charges, making them ideal for CloudFront origins. Health checks were configured with 10-second intervals and 3 consecutive failure threshold to catch distribution outages.

Environment & Deployment Configuration

Environment Variables (.env.local):

The file structure follows Vercel conventions:

# .env.local (created at deployment time)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_... (added post-deployment)
PRINTFUL_API_KEY=(from Printful dashboard)
PRINTFUL_VARIANT_BLACK_4016=... (5 variant IDs)
PRINTFUL_VARIANT_BLACK_4017=...
# ... etc

The NEXT_PUBLIC_ prefix exposes Stripe's publishable key to the browser (safe by design). Secret keys remain server-side only. Variant IDs are environment variables rather than hardcoded to enable rapid SKU changes without code deployment.

Vercel Deployment:

cd ~/Documents/repos/sites/86dfrom.com
npx vercel@latest --prod \
  --env STRIPE_SECRET_KEY="sk_live_..." \
  --env STRIPE_WEBHOOK_SECRET="whsec_..." \
  --env PRINTFUL_API_KEY="..."

Vercel automatically configures preview deployments, branch deployments, and production. The --prod