Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14 + Google Apps Script + AWS Infrastructure

What Was Done

We built a complete e-commerce storefront for 86dfrom.com—a Printful-integrated t-shirt sales site—with a modern Next.js 14 frontend, serverless backend, and AWS-hosted static fallback infrastructure. The project demonstrates a hybrid deployment pattern: Vercel for the dynamic web app, AWS S3 + CloudFront for static content delivery, and Google Apps Script for order fulfillment automation.

Architecture Overview

The site follows a multi-tier architecture:

  • Frontend Layer: Next.js 14 app running on Vercel, handling product listings, variant selection, and Stripe checkout flow
  • Order Processing: Google Apps Script (Google Cloud Platform) triggered via webhook, writing order data to a Google Sheet and interfacing with Printful API
  • Payment: Stripe (production or test keys), with webhook validation at /api/webhook
  • Product Data: Printful API for real-time variant retrieval and SKU management
  • Static Backup: AWS S3 bucket (86dfrom-static-site-prod) + CloudFront distribution for resilience if the primary Vercel deployment goes down
  • DNS: Route53 for 86dfrom.com and 86from.com (redirect only)

File Structure & Key Components

The project layout mirrors a standard Next.js monorepo with additional deployment automation:


~/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html              (Static fallback landing page)
│   └── success.html            (Order confirmation page)
├── gas/
│   ├── Code.gs                 (Google Apps Script main)
│   └── appsscript.json         (GAS manifest & OAuth scopes)
├── scripts/
│   ├── deploy.sh               (S3 + CloudFront deployment)
│   └── get-printful-variants.js (Variant ID fetcher)
└── .env.local                  (Runtime secrets: PRINTFUL_API_KEY, STRIPE_SECRET_KEY, etc.)

The Next.js app itself (deployed separately to Vercel) lives in the primary git repo and includes:

  • /app/page.tsx – Home page with product grid and variant selector
  • /app/checkout/page.tsx – Stripe checkout integration
  • /app/api/webhook – Stripe webhook endpoint for order capture
  • /app/api/variants – Backend route exposing Printful variant data (populated by get-printful-variants.js)

Technical Implementation Details

Printful Integration

The scripts/get-printful-variants.js script runs during build to fetch all available product variants from the Printful store "86Store" using the provided API key. It specifically targets the Bella+Canvas 3001 Black unisex t-shirt in five sizes (XS–2XL) and writes a JSON manifest to the frontend. This decouples product metadata from code, allowing inventory or pricing changes on Printful to propagate without redeployment.

Why this pattern: Printful's variant IDs are opaque and store-specific. Hardcoding them invites bugs when inventory changes. Fetching at build time ensures the Next.js build fails loudly if the API is unreachable—catching deployment issues early.

Google Apps Script Automation

The gas/Code.gs file contains a doPost() function that listens for incoming webhook POST requests (from Stripe or manual triggers). When invoked, it:

  1. Validates the request signature (for Stripe webhooks)
  2. Extracts order metadata (customer email, size, quantity, amount)
  3. Appends a row to a Google Sheet for fulfillment tracking
  4. Calls the Printful API to create a print order, using the pre-fetched variant IDs
  5. Logs results and errors for debugging

The appsscript.json manifest declares OAuth scopes (spreadsheets, script.external_request) needed for Google Sheets access and outbound HTTP calls to Printful.

Why Apps Script: It's serverless, avoids cold starts, integrates natively with Google Workspace (Sheets), and costs nothing at our scale. The trade-off is limited observability compared to a Lambda function, but for a small shop, the simplicity wins.

Stripe Webhook Handling

The /api/webhook route in Next.js validates incoming Stripe events using the webhook secret, ensuring only legitimate Stripe requests trigger order creation. Once validated, it makes an authenticated POST call to the Google Apps Script endpoint, passing order details. The webhook secret itself (prefixed whsec_) is stored in Vercel's environment variables and never committed to git.

Security consideration: The webhook endpoint is publicly accessible but protected by signature verification. Without it, anyone could forge orders. Stripe's signature uses HMAC-SHA256, making forgery computationally infeasible.

AWS Infrastructure Deployment

S3 & CloudFront Setup

We created two AWS resources for static hosting:

  • S3 Bucket: 86dfrom-static-site-prod – stores site/index.html and site/success.html
  • CloudFront Distribution: ID E1ABC2D3FG4HI (example) – caches S3 content at AWS edge locations worldwide, adds HTTPS, and handles compression

The bucket policy allows CloudFront's Origin Access Identity to read objects but denies direct public S3 access, forcing all traffic through CloudFront. This ensures:

  • HTTPS-only delivery (no mixed content)
  • Geo-distribution reducing latency
  • DDoS protection via AWS Shield
  • Cache invalidation control (via scripts/deploy.sh)

ACM Certificates & DNS

We requested ACM (AWS Certificate Manager) certificates for both 86dfrom.com and 86from.com, with validation via DNS CNAME records added to Route53. This avoids email validation delays and provides automatic renewal before expiry.

Route53 hosted zone for 86dfrom.com now contains:

  • A record (alias) → CloudFront distribution (for 86dfrom.com and www.86dfrom.com)
  • A record (alias) → Redirect CloudFront distribution (for 86from.com86dfrom.com)

The redirect distribution uses a CloudFront function (Lambda@Edge alternative) to intercept all 86from.com