```html

Building a Print-on-Demand T-Shirt Site: Next.js, Printful, Stripe, and AWS Infrastructure

This post documents the full-stack setup of 86dfrom.com, a Stripe-enabled print-on-demand merchandise site powered by Next.js 14, Printful API integration, and AWS CloudFront + S3 distribution. The project demonstrates a production-ready pattern for combining serverless backend (Vercel), third-party fulfillment APIs, and payment processing at scale.

Project Architecture Overview

The 86dfrom.com site is built as a Next.js 14 application with the following architecture:

  • Frontend: React/Next.js 14 running on Vercel (auto-scaling serverless)
  • Backend: Next.js API routes handling Printful product sync, Stripe checkout, and webhook ingestion
  • Fulfillment: Printful API for inventory, variants, and order fulfillment
  • Payments: Stripe for checkout and subscription webhooks
  • Static Mirror: S3 + CloudFront serving static HTML for 86from.com (domain redirect variant)
  • DNS: Route53 managing both 86dfrom.com and 86from.com zones

File Structure and Component Organization

The project is organized under /Users/cb/Documents/repos/sites/86dfrom.com/ with the following layout:


86dfrom.com/
├── site/
│   ├── index.html              # Static fallback page
│   └── success.html            # Order confirmation page
├── gas/
│   ├── Code.gs                 # Google Apps Script (if needed for sheets integration)
│   └── appsscript.json         # GAS manifest
├── scripts/
│   ├── deploy.sh               # S3 + CloudFront deployment script
│   └── get-printful-variants.js # Extracts variant IDs from Printful API
├── .env.local                  # Local secrets (Stripe, Printful keys)
└── [Next.js app root]          # Standard Next.js 14 structure
    ├── app/
    │   ├── api/
    │   │   ├── checkout/route.ts       # POST endpoint for Stripe session creation
    │   │   ├── variants/route.ts       # GET endpoint for product variants
    │   │   └── webhook/route.ts        # POST endpoint for Stripe events
    │   ├── success/page.tsx            # Order confirmation page
    │   └── page.tsx                    # Homepage with product grid
    ├── public/
    │   └── [product images, fonts]
    └── package.json

Printful API Integration

The Printful integration is handled through two key components:

Variant ID Extraction: The script scripts/get-printful-variants.js queries the Printful API to fetch available product variants for the store "86Store". The script:

  • Authenticates using the Printful API key (bearer token in Authorization header)
  • Calls GET /api/v2/stores to confirm store access
  • Iterates through product variants, filtering for the Bella+Canvas 3001 Black t-shirt (style ID 21)
  • Extracts variant IDs for sizes XS–3XL (variant IDs 4016–4020)
  • Outputs JSON mapping: { "XS": 4016, "S": 4017, "M": 4018, "L": 4019, "XL": 4020 }

These variant IDs are then stored in .env.local as:


NEXT_PUBLIC_PRINTFUL_VARIANTS_DARK_TEE={"XS":4016,"S":4017,"M":4018,"L":4019,"XL":4020}
PRINTFUL_API_KEY=[api_key_here]
PRINTFUL_STORE_ID=86Store

Backend Variant Route: The API endpoint app/api/variants/route.ts (GET) returns the parsed variant object to the frontend, allowing the React component to render size selectors dynamically without hardcoding IDs.

Stripe Payment Flow

Payment processing follows a standard Stripe checkout pattern:

  1. Frontend: User selects size and quantity, clicks "Buy Now"
  2. POST to /api/checkout: Sends { size, quantity, priceInCents }
  3. Backend creates Stripe session: Calls stripe.checkout.sessions.create() with:
    • Line items: product metadata (size, variant ID)
    • Success URL: https://86dfrom.com/success
    • Cancel URL: https://86dfrom.com
    • Payment method types: ["card"]
  4. Redirect to Stripe-hosted checkout: Browser redirected to stripe.com/pay/[session_id]
  5. Webhook ingestion: POST to /api/webhook on charge.succeeded event
    • Verifies signature using Stripe webhook secret
    • Extracts order metadata (size, variant) from session
    • Submits order to Printful via POST /api/v2/orders
    • Logs confirmation or stores in database (future enhancement)

Environment variables required:


STRIPE_SECRET_KEY=sk_live_[your_key_here]
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_[your_key_here]
STRIPE_WEBHOOK_SECRET=whsec_[generated_after_deploy]

AWS Infrastructure and Static Distribution

A secondary static site is hosted on S3 + CloudFront to serve 86from.com (single-letter variant domain) as a redirect to the main Vercel app. This pattern provides:

  • SEO isolation: Separate domain prevents duplicate content penalties
  • Zero-latency redirects: CloudFront edge locations serve 301 redirects globally
  • Lower cost: Static redirect costs ~$0.01/month vs. spinning a full server

Infrastructure created:

  • S3 bucket: 86from.com (public access, static hosting enabled)
  • ACM certificate: 86from.com (DNS validation via Route53)
  • CloudFront distribution: ID [cf_dist_id]
    • Origin: S3 bucket 86from.com.s3.us-east-1