```html

Building a Printful + Stripe T-Shirt Commerce Site: Next.js 14, CloudFront, and Google Apps Script Integration

This post documents the full-stack implementation of 86dfrom.com, a Printful-powered t-shirt e-commerce site built with Next.js 14, deployed to Vercel, and backed by AWS CloudFront + S3 for static assets. The project integrates Stripe payments, Google Apps Script for order fulfillment workflows, and environment-driven variant management.

Project Structure and Technology Stack

The codebase lives at /Users/cb/Documents/repos/sites/86dfrom.com/ with the following structure:

  • site/ — Static HTML for landing/success pages
  • gas/ — Google Apps Script for backend order processing
  • scripts/ — Deployment and utility scripts
  • Next.js 14 routes in app/ directory (standard pages/api/ pattern)

Why this stack? Next.js 14 provides server-side API routes without a separate backend. Printful's REST API handles inventory and fulfillment. Stripe manages payments. Google Apps Script (deployed as a web app) logs orders to a shared Google Sheet, creating an auditable fulfillment queue. CloudFront + S3 cache static assets globally with cache invalidation on deploy.

Next.js Routes and Stripe Integration

The build compiles cleanly with five API routes:

  • /api/webhook — Stripe webhook endpoint (receives payment_intent.succeeded events)
  • /api/variants — Returns Printful product variant IDs from environment variables
  • /api/orders — Accepts POST requests with customer data and redirects to Printful checkout
  • /api/health — Health check for monitoring
  • Additional routes for product data and pricing

Each route validates environment variables at startup. The /api/orders endpoint constructs a Printful fulfillment request, storing the order ID in a session for webhook correlation. Stripe webhook processing happens at /api/webhook, which verifies the signature using process.env.STRIPE_WEBHOOK_SECRET and calls the Google Apps Script URL to log the order.

Environment Configuration and Build Pipeline

The project uses a .env.local file (git-ignored) populated at deployment time with:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
PRINTFUL_STORE_ID=...
GAS_WEBHOOK_URL=...

The next build command compiles all routes with TypeScript checking enabled. A clean build confirms no runtime errors in API handlers. Environment variables are injected at Vercel build time via the Vercel dashboard UI (Settings → Environment Variables), not committed to the repository.

Printful Variant Management

Printful product variant IDs are fetched via a one-time script at scripts/get-printful-variants.js:

node scripts/get-printful-variants.js

This script calls the Printful API with the store token and product ID, outputting variant IDs for Bella+Canvas 3001 in Black (SKU variants 4016–4020 covering XS–3XL). Variant IDs are stored in .env.local as comma-separated values, allowing the frontend to construct variant-specific checkout links without additional API calls.

Why pre-compute variants? Printful variant IDs are static for a given product. Caching them in environment variables reduces API calls, speeds up checkout, and makes the variant list auditable in version control (or at least documented).

Infrastructure: AWS, Route53, ACM, and CloudFront

Static assets are hosted on AWS:

  • S3 Bucket: 86dfrom-site-assets — stores site/index.html, site/success.html, and public static files
  • CloudFront Distribution: Primary distribution for 86dfrom.com (public, caching enabled)
  • Route53 Hosted Zone: DNS records for 86dfrom.com point to CloudFront
  • ACM Certificate: Issued for 86dfrom.com (DNS validation via Route53 CNAME records)

Redirect handling: A second CloudFront distribution (with Lambda@Edge function) handles the 86from.com domain (typo variant), redirecting all traffic to 86dfrom.com

S3 bucket policy restricts access to the CloudFront origin identity only—no public reads. This ensures traffic flows through CloudFront's cache layer and WAF integration (if enabled later).

Deployment Pipeline

The deployment flow is:

  1. Build Next.js locally or in Vercel CI: npm run build
  2. Deploy to Vercel: npx vercel@latest --prod (Vercel assigns a *.vercel.app URL and edge functions)
  3. Set environment variables in Vercel dashboard (Settings → Environment Variables)
  4. Deploy static assets to S3: bash scripts/deploy.sh
  5. Invalidate CloudFront cache: aws cloudfront create-invalidation --distribution-id E123... --paths "/*"
  6. Update Route53 DNS if needed (one-time, unless Vercel IP changes)

scripts/deploy.sh uses the AWS CLI to sync site/ to s3://86dfrom-site-assets/ with cache headers for long-lived assets. HTML files are set to no-cache, ensuring users always fetch the latest index.

Google Apps Script for Order Logging

The gas/Code.gs script (deployed to Google Apps Script) exposes a web app endpoint that accepts POST requests with order data (customer email, name, Stripe payment ID, Printful order ID). It appends rows to a Google Sheet, creating an auditable fulfillment log.

Why Apps Script? It's lightweight, integrates with Google Sheets (familiar to non-technical staff), and requires no additional infrastructure. The script's web app URL is stored in process.env.GAS_WEBHOOK_URL and called asynchronously after Stripe webhook verification.

The appsscript.json manifest declares OAuth scopes for Sheets access and a deployable web app.

Key Decisions and Trade-offs

  • Next.js over Express: Vercel's native deployment, automatic HTTPS, edge routing, and built-in API middleware reduce operational overhead.
  • CloudFront + S