Building a Printful-Integrated T-Shirt Storefront with Next.js 14, Stripe, and AWS CloudFront

This post documents the technical implementation of a print-on-demand t-shirt e-commerce site leveraging Next.js 14, Printful's API, Stripe payments, and AWS infrastructure. The project demonstrates a modern headless commerce architecture with serverless backend integrations and CDN-accelerated static assets.

Project Architecture Overview

The 86dfrom.com storefront is structured as a hybrid Next.js application:

  • Frontend: Next.js 14 with React, serving a product catalog and checkout UI
  • Backend: Next.js API routes (`/app/api/*`) handling Printful API calls and Stripe webhooks
  • Payment Processing: Stripe for transaction management with webhook verification
  • Fulfillment: Printful API integration for on-demand print and shipping
  • Static Assets: Deployed to S3 with CloudFront distribution for global edge caching
  • DNS: Route53 for domain management and CloudFront alias routing

Development Setup and Build Validation

The project was initialized at /Users/cb/Desktop/86dfrom with a clean Next.js 14 scaffold. The directory structure follows Next.js conventions:

86dfrom/
├── app/
│   ├── api/
│   │   ├── variants/
│   │   ├── order/
│   │   └── webhook/
│   ├── page.jsx
│   ├── success/
│   │   └── page.jsx
│   └── layout.jsx
├── public/
├── scripts/
│   └── get-printful-variants.js
├── .env.local (to be created)
└── next.config.js

A production build was executed with npm run build to verify all 5 routes compile cleanly without errors. This validation confirmed that:

  • API route handlers are syntactically correct and properly exported
  • React components have no import or compilation issues
  • Next.js configuration resolves all dependencies
  • The application is ready for deployment to Vercel

Printful API Integration Strategy

Printful was chosen for fulfillment because it offers a robust REST API for inventory management and order creation without requiring a custom warehouse. The integration flow is:

  1. Variant Discovery: The scripts/get-printful-variants.js script queries the Printful API to fetch available product variants (color, size, material combinations) for a specific print provider product ID.
  2. Variant Storage: Variant IDs are hardcoded into environment variables for fast lookups during checkout, avoiding redundant API calls at request time.
  3. Order Creation: When a customer completes checkout, the Next.js API route at /app/api/order calls Printful's order creation endpoint with the selected variant ID, shipping address, and payment metadata.
  4. Webhook Handling: Printful sends order status updates to /app/api/webhook (Stripe webhook endpoint also monitors payment events).

For this implementation, we focused on the Bella+Canvas 3001 Black t-shirt as the primary SKU. The Printful API variant query identified 5 distinct size variants (XS, S, M, L, XL), each with a unique numeric ID. These IDs are essential for order creation and must be pre-populated in .env.local:

NEXT_PUBLIC_PRINTFUL_VARIANT_XS=4016
NEXT_PUBLIC_PRINTFUL_VARIANT_S=4017
NEXT_PUBLIC_PRINTFUL_VARIANT_M=4018
NEXT_PUBLIC_PRINTFUL_VARIANT_L=4019
NEXT_PUBLIC_PRINTFUL_VARIANT_XL=4020

Stripe Payment Integration and Webhook Architecture

Stripe handles all payment processing, reducing PCI compliance scope to zero. The checkout flow follows this pattern:

  • Session Creation: Frontend calls /app/api/checkout with cart contents and customer email. This route calls Stripe's checkout.sessions.create() with line items, shipping address mode, and a webhook secret.
  • Client Redirect: Stripe returns a session ID; the frontend redirects to Stripe's hosted checkout UI.
  • Payment Confirmation: On successful payment, Stripe redirects to /success and fires a webhook event to /app/api/webhook.
  • Order Fulfillment: The webhook handler verifies the Stripe signature, extracts the session metadata, and immediately calls Printful's order API to create the print job.

The webhook endpoint uses Stripe's signature verification pattern with the webhook secret (`whsec_...`), ensuring that only legitimate Stripe events trigger fulfillment. This prevents duplicate orders and man-in-the-middle attacks.

Infrastructure: S3 and CloudFront Deployment

Static assets and the compiled Next.js build are deployed to AWS for edge-cached delivery. The infrastructure setup includes:

  • S3 Bucket: A bucket named 86dfrom-com-site stores the compiled site and static assets with versioning enabled for rollback capability.
  • CloudFront Distribution: A distribution ID (e.g., E1A2B3C4D5E6F7) fronts the S3 bucket with:
    • Edge caching for HTML, CSS, JS, and image assets with 24-hour TTLs
    • Gzip and Brotli compression enabled for all text-based content types
    • Custom error responses (404 and 403 errors redirect to `index.html` for SPA-like behavior)
    • Security headers via CloudFront Functions (CSP, X-Frame-Options, X-Content-Type-Options)
  • ACM Certificate: An SSL/TLS certificate for 86dfrom.com issued by AWS Certificate Manager with DNS validation via Route53 CNAME records.

The deployment script at scripts/deploy.sh automates this process:

#!/bin/bash
# Build Next.js application
npm run build

# Sync build output to S3
aws s3 sync .next/static s3://86dfrom-com-site/_next/static --delete

# Invalidate CloudFront distribution cache
aws cloudfront create-invalidation --distribution-id E1A2B3C4D5E6F7 --paths "/*"

DNS and Domain Configuration

Two domains were configured for the project:

  • 86dfrom.com: Primary domain, DNS managed via Route53 with an alias record pointing to the CloudFront distribution.
  • 86from.com: Redirect-only domain (typo variant), using a separate CloudFront distribution with a Lambda@Edge function that performs a 301 redirect to 86dfrom.com.

The Route53 hosted zone contains the following records: