Building a Printful T-Shirt Store on Next.js 14: Full-Stack Setup with AWS CloudFront, Route53, and Google Apps Script

This post documents the complete technical setup for 86dfrom.com, a Printful-integrated t-shirt e-commerce site built on Next.js 14 with Stripe payments, AWS CloudFront CDN delivery, and Google Apps Script backend automation. The project demonstrates a modern serverless architecture pattern combining multiple SaaS platforms with custom infrastructure.

Project Structure and Technology Stack

The project is organized into three distinct deployment surfaces:

  • Next.js Frontend (/site): React 19 with App Router, deployed to Vercel
  • Google Apps Script Backend (/gas): Order processing and fulfillment automation
  • Static Assets: Hosted on AWS S3 with CloudFront CDN, managed via Route53

The development environment is configured in /Users/cb/Documents/repos/sites/86dfrom.com/ with this structure:

86dfrom.com/
├── site/               # Next.js 14 application
│   ├── index.html      # Landing page template
│   ├── success.html    # Post-purchase confirmation
│   └── [...routes]     # API endpoints
├── gas/                # Google Apps Script
│   ├── Code.gs         # Fulfillment automation
│   ├── appsscript.json # GAS manifest
│   └── .clasp.json     # clasp deployment config
└── scripts/            # Infrastructure automation
    └── deploy.sh       # S3/CloudFront deployment script

Environment Configuration and Build Verification

The Next.js 14 build compiles cleanly with all five API routes:

  • /api/create-checkout – Stripe checkout session creation
  • /api/webhook – Stripe event handler
  • /api/get-variants – Printful variant data endpoint
  • /api/[...routes] – Dynamic route catch-all
  • Static product page routes

The build was verified with npm run build, producing optimized output ready for Vercel deployment. Configuration is managed via environment variables in .env.local:

# Required environment variables
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_[...]
STRIPE_SECRET_KEY=sk_live_[...]
STRIPE_WEBHOOK_SECRET=whsec_[...]
PRINTFUL_API_KEY=[token]
PRINTFUL_STORE_ID=[numeric-id]

Printful API Integration

The Printful integration uses the 86Store account under the "Hello Dangerous" workspace with API scopes covering all stores. Variant data is fetched and cached using a script pattern at scripts/get-printful-variants.js:

// Fetches variant IDs for Bella+Canvas 3001 Black t-shirts
// Required for /api/create-checkout to function
const variants = {
  "xs": 4016,
  "s": 4017,
  "m": 4018,
  "l": 4019,
  "xl": 4020
}

These variant IDs are hardcoded into the checkout flow, eliminating runtime Printful API calls at payment time and reducing latency.

AWS Infrastructure: S3, CloudFront, and Route53

The static delivery pipeline uses three AWS services working in concert:

S3 Bucket Configuration

An S3 bucket named 86dfrom.com serves as the origin for CloudFront. Bucket policy restricts access exclusively to the CloudFront distribution via origin access identity (OAI), preventing direct S3 HTTP access:

# Bucket: 86dfrom.com
# Policy: CloudFront OAI only
# Versioning: Enabled for rollback capability
# Public access: Blocked entirely

CloudFront Distribution for Primary Domain

The primary distribution (d[...].cloudfront.net) serves 86dfrom.com with:

  • Origin: S3 bucket via OAI
  • SSL/TLS: AWS Certificate Manager cert for 86dfrom.com
  • CNAME: 86dfrom.com
  • Default root object: index.html
  • Cache behaviors: 24-hour TTL for HTML, 365-day for versioned assets
  • Compression: Gzip for all text content

ACM certificate validation required DNS CNAME records added to Route53:

# Route53 validation records (auto-generated by ACM)
_[random].86dfrom.com CNAME _[random].acm-validations.aws.

CloudFront Redirect Distribution for Variant Domain

A second distribution handles the 86from.com typo variant, implementing client-side redirect logic via CloudFront Functions. The function checks the Host header and redirects to the canonical domain:

// CloudFront Function: redirect-to-canonical
if (request.headers.host.value === '86from.com') {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: {
      'location': { value: 'https://86dfrom.com' + request.uri }
    }
  }
}

This function is attached to the viewer-request event on the redirect distribution, reducing load on origin infrastructure.

Route53 DNS Configuration

Two A records in the hosted zone for 86dfrom.com direct traffic to CloudFront:

# Primary domain
86dfrom.com A [alias] → d[...].cloudfront.net (primary distribution)

# Redirect variant
86from.com A [alias] → d[...].cloudfront.net (redirect distribution)

Both use Route53 alias records, eliminating additional DNS lookups and providing health checks at the CloudFront level.

Deployment Pipeline

The deployment script at scripts/deploy.sh automates S3 sync and CloudFront cache invalidation:

#!/bin/bash
aws s3 sync ./site s3://86dfrom.com --delete
aws cloudfront create-invalidation \
  --distribution-id [ID] \
  --paths "/*"

This pattern ensures:

  • Atomic updates: Files are synced before cache invalidation
  • Full purge: /* invalidates all CloudFront edge locations globally
  • Deletion safety: --delete removes orphaned files from S3

Stripe Payment Integration

Checkout flow is implemented in /api/create-checkout:

  • Accepts cart items with size selection
  • Maps size to Printful variant ID
  • Creates Stripe checkout session with line items