```html

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

This post documents the complete infrastructure and application setup for 86dfrom.com, a print-on-demand t-shirt storefront that integrates Printful's fulfillment API with Stripe payment processing. The architecture spans Next.js 14 on Vercel, AWS S3/CloudFront for static assets, Google Sheets for variant management, and Google Apps Script for backend webhook handling.

Project Overview and Architecture Decisions

The 86dfrom project is a single-product e-commerce site selling Bella+Canvas 3001 black t-shirts through Printful's print-on-demand service. Rather than a traditional monolithic backend, we chose a hybrid approach:

  • Next.js 14 (Vercel) handles the web frontend and API routes for Stripe webhook processing
  • Google Apps Script serves as the webhook handler and order logging mechanism, persisting data to Google Sheets
  • AWS S3 + CloudFront distributes static assets and serves as a landing page fallback
  • Printful API provides variant IDs and fulfillment capabilities
  • Stripe manages payments and handles subscription/one-time payment logic

This separation of concerns allows us to use managed services (Vercel, Stripe, Printful) for their core competencies while keeping the data pipeline simple: Stripe → Vercel API route → Google Apps Script → Google Sheets → analytics/fulfillment workflow.

File Structure and Configuration

The project repository is organized as follows:

~/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html          # Static landing page (deployed to S3)
│   └── success.html        # Post-purchase confirmation
├── gas/
│   ├── Code.gs             # Google Apps Script webhook handler
│   └── appsscript.json     # GAS deployment manifest
├── scripts/
│   └── deploy.sh           # S3 deployment and CloudFront invalidation
└── .env.local              # Local environment variables (Vercel secrets)

The .env.local file contains three critical environment variables:

  • NEXT_PUBLIC_STRIPE_KEY — Publishable key (safe for client-side)
  • STRIPE_SECRET_KEY — Secret key for server-side operations
  • STRIPE_WEBHOOK_SECRET — Webhook signing secret for /api/webhook

Variant Management and Printful Integration

Rather than hardcoding product variants, we fetch them dynamically from Printful's API. The Printful account (86Store under the "Hello Dangerous" business) contains five variant IDs for the Bella+Canvas 3001 black t-shirt:

  • 4016 — Size XS
  • 4017 — Size S
  • 4018 — Size M
  • 4019 — Size L
  • 4020 — Size XL

These IDs are populated via the script scripts/get-printful-variants.js, which queries the Printful API endpoint and stores results in .env.local. The Printful API key grants full scope across all stores in the account, enabling both product queries and order creation.

AWS Infrastructure: S3, CloudFront, and DNS

Primary Domain (86dfrom.com):

  • S3 Bucket: 86dfrom-com-site (region: us-east-1)
  • CloudFront Distribution: Serves index.html and success.html with HTTPS via ACM certificate
  • Route53 Hosted Zone: Contains A record aliasing to CloudFront distribution

The S3 bucket is configured as a static website endpoint with a bucket policy that restricts access to CloudFront's Origin Access Identity (OAI), preventing direct S3 access. This ensures all traffic flows through CloudFront's caching layer and DDoS protection.

Subdomain Redirect (86from.com → 86dfrom.com):

A secondary CloudFront distribution handles the typo domain 86from.com, using a Lambda@Edge function to redirect all requests to the primary domain:

function handler(event) {
  const request = event.Records[0].cf.request;
  return {
    status: '301',
    statusDescription: 'Moved Permanently',
    headers: {
      'location': [{
        key: 'Location',
        value: 'https://86dfrom.com' + request.uri
      }]
    }
  };
}

This CloudFront function is published to the us-east-1 region (required for Lambda@Edge) and attached to the viewer request event.

Google Apps Script Webhook Handler

The file gas/Code.gs contains a doPost() function that receives webhook events from Stripe. The handler:

  1. Verifies the webhook signature using the secret key
  2. Parses the event JSON and extracts order metadata (customer email, product variant, quantity)
  3. Logs the transaction to a Google Sheet for fulfillment tracking
  4. Calls the Printful API to create an order with the correct variant ID
  5. Returns a 200 status to acknowledge receipt (Stripe requires this within 300 seconds)

The appsscript.json manifest specifies the runtime version and permissions:

{
  "timeZone": "America/New_York",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "dependencies": {
    "libraries": []
  }
}

Deployment uses the clasp CLI tool with a .clasp.json file pointing to the Apps Script project ID.

Deployment Pipeline and Build Process

Next.js Build: The Vercel project automatically runs npm run build, which compiles all 5 API routes:

  • /api/webhook — Stripe webhook endpoint
  • /api/checkout — Initiates Stripe Checkout session
  • /api/variants — Returns available sizes and prices
  • /api/health — Health check endpoint
  • /api/orders — Retrieves order history (optional)

The build is configured to output static files to .next/static/ with source maps disabled in production.

Static Asset Deployment: The script scripts/deploy.sh handles S3 and CloudFront updates:

#!/bin/bash
# Sync site/ directory to S3
aws s3 sync ./