```html

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

This post documents the complete infrastructure and application setup for 86dfrom.com, a serverless t-shirt e-commerce site that integrates Printful's on-demand printing API with Stripe payments. The project demonstrates a modern full-stack pattern: Next.js 14 API routes for backend logic, CloudFront + S3 for static hosting, Route53 for DNS, and Google Apps Script for order management automation.

Architecture Overview

The 86dfrom project uses a hybrid hosting model:

  • Next.js 14 application deployed to Vercel with API routes handling Printful and Stripe integration
  • Static marketing site (fallback/redirect) served from S3 + CloudFront for 86from.com variant domain
  • Google Apps Script (GAS) for webhook processing and order notifications
  • Route53 managing DNS for both 86dfrom.com and 86from.com redirect
  • ACM certificates providing SSL/TLS for both domains

This separation allows the Vercel-hosted app to handle dynamic commerce while maintaining a lightweight static fallback and permitting future migration without downtime.

Project Structure and File Layout

The development workspace is organized as follows:

~/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html          (static marketing page)
│   └── success.html        (post-purchase confirmation)
├── gas/
│   ├── Code.gs             (Google Apps Script webhook handler)
│   └── appsscript.json     (GAS manifest with scopes)
└── scripts/
    └── deploy.sh           (S3 + CloudFront invalidation)

The main Next.js application lives at /Users/cb/Desktop/86dfrom and is version-controlled separately, with a clean build verified against all five API routes: /api/variants, /api/order, /api/webhook, /api/health, and / (home).

Infrastructure Components

S3 and CloudFront Setup

Two S3 buckets were provisioned to separate concerns:

  • 86dfrom.com bucket: Hosts the Next.js static exports and serves through a CloudFront distribution
  • 86from.com bucket: Minimal static site for the typo-domain redirect (common for t-shirt sites)

Each bucket has a bucket policy restricting access to its paired CloudFront Origin Access Identity (OAI), preventing direct S3 access and centralizing caching logic. The CloudFront distributions are configured with:

  • Gzip and Brotli compression for HTML, CSS, and JavaScript
  • Browser cache TTL of 3600 seconds for static assets
  • Origin cache control respecting S3 object metadata
  • A CloudFront Function (Edge Lambda equivalent) for redirects on 86from.com

The redirect distribution on 86from.com uses a CloudFront Function that examines incoming requests and rewrites them to the primary 86dfrom.com domain, preserving query strings for tracking.

DNS and Certificate Management

Route53 hosted zone for both domains includes:

  • 86dfrom.com A record: Alias to CloudFront distribution (no TTL, AWS-managed)
  • 86from.com A record: Alias to redirect CloudFront distribution
  • ACM validation CNAMEs: DNS records proving domain ownership for both certificates

ACM certificates were requested for both 86dfrom.com and 86from.com, with validation records automatically added to Route53. Certificate renewal is handled automatically by AWS after validation passes.

Deployment Pipeline

The scripts/deploy.sh script automates the S3 + CloudFront workflow:

#!/bin/bash
# Sync site/ directory to S3
aws s3 sync site/ s3://86dfrom.com --delete

# Invalidate CloudFront cache for all objects
aws cloudfront create-invalidation \
  --distribution-id DISTRIBUTION_ID \
  --paths "/*"

This pattern is identical to the dangerouscentaur.com deployment, ensuring consistency across projects. The script is made executable via chmod +x and can be run locally or in CI/CD pipelines.

Application Integration Points

Printful API Integration

The Next.js app communicates with Printful's REST API to:

  • Fetch available product variants (stored in .env.local as variant IDs)
  • Create print-ready orders with customer and shipping information
  • Poll order status and track fulfillment

A companion script scripts/get-printful-variants.js queries Printful's GET /products/{id}/variants endpoint to extract variant IDs for the Bella+Canvas 3001 Black t-shirt, populating the environment configuration. The endpoint returns SKU, print areas, and pricing tier information used in the storefront UI.

Stripe Webhook Integration

The /api/webhook route accepts POST requests from Stripe's charge.succeeded events. The webhook secret (prefixed whsec_) is configured in Vercel's environment and used to cryptographically verify webhook authenticity:

// Verify Stripe signature
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
  body,
  sig,
  STRIPE_WEBHOOK_SECRET
);

// Process payment event
if (event.type === 'charge.succeeded') {
  // Create Printful order
  // Trigger Google Apps Script notification
}

This webhook-driven pattern decouples payment processing from order creation, allowing for asynchronous fulfillment and retry logic.

Google Apps Script for Order Management

The gas/Code.gs file contains a web app that receives POST requests from the Stripe webhook handler. The GAS manifest (appsscript.json) declares required scopes:

  • https://www.googleapis.com/auth/spreadsheets — write order details to a Google Sheet
  • https://www.googleapis.com/auth/mail — send order confirmation emails

Deploying the GAS app generates a unique URL (format: https://script.google.com/macros/d/{SCRIPT_ID}/userweb) which is registered in the Next.js environment as the webhook callback target. This allows non-technical team members to monitor orders in a familiar Google Sheet interface.

Key Technical Decisions

Why Vercel for the App, S3+CloudFront for Static Content?