```html

Building a Printful-Integrated T-Shirt E-Commerce Site: Next.js 14, Stripe, and AWS Infrastructure

Overview

This post documents the complete infrastructure and application setup for 86dfrom.com, a Printful-integrated t-shirt storefront built on Next.js 14. The project demonstrates a modern serverless architecture pattern combining edge-deployed Next.js applications with AWS static hosting, API gateway abstractions, and payment processing through Stripe.

What Was Built

The 86dfrom.com project is a full-stack e-commerce application with the following components:

  • Frontend: Next.js 14 application with server components and API routes
  • Backend: Next.js API routes interfacing with Printful and Stripe APIs
  • Payment Processing: Stripe integration with webhook handlers
  • Product Variants: Dynamic variant fetching from Printful's Bella+Canvas 3001 Black t-shirt
  • Infrastructure: Multi-CDN deployment with Vercel for dynamic content and S3/CloudFront for static assets

Project Structure

The codebase follows a standard Next.js 14 monolithic structure:

~/Documents/repos/sites/86dfrom.com/
├── site/                    # Static HTML export (backup)
│   ├── index.html
│   └── success.html
├── gas/                     # Google Apps Script (form submission handler)
│   ├── Code.gs
│   └── appsscript.json
├── scripts/
│   ├── deploy.sh           # CloudFront invalidation script
│   └── get-printful-variants.js  # Variant ID fetcher
├── .env.local              # Environment variables (gitignored)
└── .clasp.json             # Google Apps Script project config

The main Next.js application directory structure (inferred from build process) contains:

  • /app — App Router with pages and API routes
  • /app/page.tsx — Homepage with product display
  • /app/api/variants — Endpoint returning Printful variant IDs
  • /app/api/checkout — Stripe session creation endpoint
  • /app/api/webhook — Stripe webhook receiver for order confirmation
  • /public — Static assets (fonts, images)

Environment Configuration

The .env.local file centralizes all external service credentials:

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

Why this structure: Environment variables separate secrets from code and allow environment-specific configuration. The NEXT_PUBLIC_ prefix marks variables safe to expose to the browser (publishable Stripe key only). All secret keys remain server-side only.

Printful Integration

The application fetches dynamic variant data from Printful's API. The Bella+Canvas 3001 Black t-shirt has five size variants (XS through XL) with consistent pricing. The scripts/get-printful-variants.js script performs an initial discovery:

node scripts/get-printful-variants.js

This script:

  • Authenticates with the Printful API using the token in PRINTFUL_API_KEY
  • Lists all products in the 86Store
  • Extracts the Bella+Canvas 3001 product ID
  • Maps each size variant to a Printful internal variant ID
  • Outputs variant IDs for hardcoding or dynamic retrieval

The /app/api/variants endpoint caches these mappings and serves them to the frontend, allowing the product selector to display real-time pricing from Printful without exposing the API key to clients.

Stripe Payment Flow

The checkout flow follows Stripe's recommended pattern:

  1. User selects size and quantity on the homepage
  2. JavaScript submits a POST to /app/api/checkout
  3. The backend creates a Stripe Checkout Session with line items and metadata
  4. The session ID is returned and redirected to Stripe's hosted checkout
  5. Upon completion, Stripe POSTs to /app/api/webhook
  6. The webhook handler validates the signature and updates order status

Why Stripe Checkout: Hosted checkout abstracts PCI compliance from our application. Stripe handles card tokenization, fraud detection, and encryption. We only ever receive validated, confirmed payment events.

AWS Infrastructure

S3 Bucket: 86dfrom.com (us-east-1)

Configured as a static website host with the following policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::86dfrom.com/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/CF_DIST_ID"
        }
      }
    }
  ]
}

Why S3 + CloudFront: Separates static asset hosting from dynamic application logic. S3 provides cheap, reliable object storage; CloudFront adds a global CDN layer with edge caching, DDoS protection, and geo-routing. This pattern reduces load on the main application and improves time-to-first-byte for visitors worldwide.

CloudFront Distributions:

  • Primary (86dfrom.com): Points to S3 origin, caches HTML/CSS/JS with 1-year versioning headers
  • Redirect (86from.com → 86dfrom.com): Uses CloudFront Functions to perform domain rewriting at the edge

The redirect function (deployed via Lambda@Edge integration) intercepts requests to the typo domain and rewrites them:

function handler(event) {
  return {
    statusCode: 301,
    headers: {
      'location': { value: 'https://86dfrom.com' + event.request.uri }
    }
  };
}

Why edge rewriting: Handles domain aliases (common typos, alternate branding) at CloudFront's edge locations, eliminating extra hops. The redirect is cached, so repeated requests from the same visitor don't re-evaluate the function.

DNS Configuration (Route53)

Both domains resolve via Route53 hosted zone records: