Building a Printful-Integrated T-Shirt Commerce Site on Next.js 14 with AWS CloudFront and Stripe

Over the past session, we built out a complete e-commerce application for 86dfrom.com—a t-shirt dropshipping site powered by Printful, Stripe, and Next.js 14. This post covers the architecture decisions, infrastructure setup, and integration patterns we used to create a production-ready store.

Project Structure & Tech Stack

The application lives in /Users/cb/Documents/repos/sites/86dfrom.com/ and consists of three main layers:

  • Frontend: Next.js 14 React application in /site with static HTML fallbacks
  • Backend: Next.js API routes at /app/api/ handling Printful and Stripe integrations
  • Infrastructure: AWS S3, CloudFront, Route53, and ACM for DNS, CDN, and SSL
  • Deployment Automation: Bash scripts in /scripts/deploy.sh for CI/CD

The application follows a typical serverless JAMstack pattern: static assets and HTML are served from CloudFront, dynamic API requests hit Vercel's serverless functions, and external integrations (Printful, Stripe) handle fulfillment and payments.

Why Printful for Fulfillment?

Printful's API eliminates inventory management entirely. Rather than holding stock, we:

  • Query Printful's catalog to fetch available products and variants
  • Retrieve variant IDs (e.g., 4016, 4017, 4018, 4019, 4020 for Bella+Canvas 3001 Black in sizes XS–2XL)
  • Pass variant IDs to Printful at checkout to create orders on-demand
  • Printful handles printing, packing, and shipping directly to customers

This is implemented in /app/api/checkout.js, which reads variant IDs from environment variables and constructs a Printful order payload.

Environment & API Key Management

We created .env.local with the following structure:

PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
STRIPE_SECRET_KEY=sk_live_... (or sk_test_... for staging)
STRIPE_PUBLISHABLE_KEY=pk_live_...
NEXT_PUBLIC_STRIPE_KEY=pk_live_...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020

Key points:

  • PRINTFUL_API_KEY is account-wide with all scopes across all stores (86Store in this case)
  • STRIPE_SECRET_KEY never leaves the server; it's only referenced in API routes
  • NEXT_PUBLIC_STRIPE_KEY is safe to expose to the browser—it's the publishable key
  • Variant IDs are pre-fetched from Printful and hardcoded to avoid runtime API calls on every page load

Infrastructure: DNS, SSL, and CDN

We set up a complete AWS infrastructure stack for 86dfrom.com and configured a redirect domain for 86from.com (typo-squatting protection):

Primary Domain (86dfrom.com)

  • S3 Bucket: 86dfrom-com-site (region: us-east-1, versioning enabled)
  • ACM Certificate: Requested for 86dfrom.com and *.86dfrom.com with DNS validation via Route53
  • CloudFront Distribution: Points to S3 origin with HTTP/2, gzip compression, and cache invalidation
  • Route53 Hosted Zone: Public zone for 86dfrom.com with A record aliased to CloudFront distribution

Redirect Domain (86from.com – typo protection)

  • S3 Bucket: 86from-com-redirect (region: us-east-1)
  • CloudFront Function: Custom redirect function (viewer request) that returns HTTP 301 to 86dfrom.com
  • CloudFront Distribution: Attached to the function for all requests matching 86from.com
  • Route53 A Record: 86from.com aliased to the redirect CloudFront distribution

The CloudFront Function (deployed to 86from-com-cf-function) uses this logic:

function handler(request) {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: {
      location: {
        value: 'https://86dfrom.com'
      }
    }
  };
}

This is far more efficient than maintaining a second site—CloudFront handles the redirect at the edge in milliseconds.

Deployment Pipeline

The deployment script at /scripts/deploy.sh handles:

  1. Build verification: Runs npm run build to ensure Next.js compiles cleanly (all 5 routes: /, /checkout, /success, /api/checkout, /api/webhook)
  2. Vercel deployment: npx vercel@latest --prod pushes the app to production serverless functions
  3. S3 sync: Syncs static assets to 86dfrom-com-site S3 bucket
  4. CloudFront invalidation: Invalidates cache with /* pattern to force edge nodes to pull fresh content

The two-destination approach (Vercel for dynamic routes, S3/CloudFront for static assets) is intentional. It provides:

  • Performance: Static HTML, CSS, and fonts are served from CloudFront's global edge network
  • Redundancy: API routes fail over gracefully if Vercel is down (the static site still loads)
  • Cost efficiency: CloudFront bandwidth is cheaper than Vercel for high-traffic static content
  • Control: We own the S3 bucket and can audit all assets; no vendor lock-in

Stripe Integration & Webhook Architecture

Stripe is integrated in two places:

  1. /app/api/checkout.js: Creates a Stripe Checkout Session and redirects to Stripe's hosted page