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

This post documents the technical implementation of 86dfrom.com, a custom t-shirt e-commerce site built on Next.js 14 with Printful fulfillment integration, Google Apps Script backend automation, and a multi-region CDN serving both the storefront and a redirect domain.

Architecture Overview

The project comprises three core components:

  • Next.js 14 Application — storefront and checkout flow, deployed to Vercel
  • Google Apps Script — order processing and fulfillment automation via Printful API
  • AWS Infrastructure — S3 + CloudFront CDN for static assets and redirect handling; Route53 for DNS

This hybrid approach decouples the dynamic storefront (Vercel) from static asset delivery and domain routing (AWS), allowing independent scaling and failover.

Project Structure

The development workspace was organized as follows:

/Users/cb/Desktop/86dfrom/
├── site/
│   ├── index.html          (redirect/landing fallback)
│   └── success.html        (order confirmation page)
├── gas/
│   ├── Code.gs             (Google Apps Script main handler)
│   └── appsscript.json     (GAS manifest)
├── scripts/
│   └── deploy.sh           (S3 + CloudFront deployment)
└── .env.local              (secrets: Stripe, Printful, Webhook)

This mirrors the production layout at ~/Documents/repos/sites/86dfrom.com/, ensuring parity between development and deployment targets.

Printful Integration

Printful handles fulfillment for Bella+Canvas 3001 blank t-shirts (Black variant, sizes XS–3XL). The integration workflow:

  1. Fetch all available product variant IDs via Printful REST API
  2. Store variant IDs in environment variables (referenced in .env.local)
  3. Customer selects size → Next.js API route queries variant ID → passes to checkout
  4. Order created in Printful via webhook handler in Google Apps Script

The Printful API key was provisioned with full scopes across all stores in the dangerouscentaur account. Variant discovery is accomplished via the scripts/get-printful-variants.js script, which makes authenticated requests to https://api.printful.com/products and filters for the Black 3001 variant across size ranges 4016–4020.

Why this pattern? Storing variant IDs as environment constants avoids repeated API calls during checkout, reducing latency and API rate-limit exposure. Printful's variant catalog is stable; changes are rare and can be re-run on-demand.

Next.js 14 Build and Deployment

The Next.js application compiles cleanly with zero warnings. The build output includes five API routes:

  • pages/api/checkout.js — initiates Stripe payment intent
  • pages/api/webhook.js — receives Stripe webhook events (payment confirmation)
  • pages/api/create-order.js — writes order to Google Sheet for GAS processing
  • pages/api/variants.js — returns available Bella+Canvas variants as JSON
  • pages/api/health.js — liveness probe for deployment verification

Deployment to Vercel is straightforward: environment variables (Stripe secret key, Printful API key, Google Apps Script webhook URL) are configured in Vercel's project settings, then invoked via npx vercel@latest --prod. This deploys the Next.js app to Vercel's edge network, making it available at a stable Vercel domain (e.g., 86dfrom.vercel.app) until custom DNS is routed.

AWS Infrastructure: S3, CloudFront, Route53, and ACM

Static assets and domain redirect logic are hosted on AWS:

S3 Bucket for 86dfrom.com

A bucket named 86dfrom-com-web was created in us-east-1 with the following configuration:

  • Block all public access disabled (objects require explicit public-read ACL or bucket policy)
  • Bucket policy allows CloudFront origin access identity (OAI) to read all objects
  • Static website hosting disabled (CloudFront is the only public interface)
  • Versioning enabled for rollback capability

The bucket policy grants s3:GetObject only to the CloudFront OAI principal, preventing direct HTTP access and forcing requests through the CDN.

CloudFront Distribution for 86dfrom.com

Distribution ID: E1ABCD2EF3GH4I (example; actual ID varies). Configuration:

  • Origin: 86dfrom-com-web.s3.us-east-1.amazonaws.com
  • Origin Access Identity: Restricts S3 bucket to CloudFront only
  • Default Root Object: index.html
  • Cache Behaviors:
    • /api/* → no cache, proxy to Vercel origin
    • /*.html → 1-hour cache with Content-Type text/html
    • /static/* → 30-day immutable cache
    • Default → 24-hour cache
  • Viewer Protocol Policy: Redirect HTTP → HTTPS
  • HTTP/2 and HTTP/3: Enabled

Why this cache hierarchy? API routes must never cache (Stripe webhooks, order creation) to ensure freshness. HTML pages cache briefly to absorb traffic spikes without revalidation. Static assets use long TTLs since they're content-addressed (e.g., next-bundle-hash.js). This tiering reduces origin load while maintaining correctness.

SSL/TLS via ACM

An AWS Certificate Manager (ACM) certificate was requested for both 86dfrom.com and *.86dfrom.com. Validation used DNS CNAME records added to Route53:

_acmvalidation.86dfrom.com CNAME _validation.acm-validations.aws.
_acmvalidation.86dfrom.com CNAME _other-validation.acm-validations.aws.  (for wildcard)

The certificate is attached to the CloudFront distribution, enabling HTTPS without paying for an additional SSL certificate (CloudFront handles TLS termination).

Route53 DNS Configuration

Two hosted zones manage domain resolution:

  • 86dfrom.com (primary storefront) — A alias record pointing to CloudFront distribution E1