```html

Building a Printful T-Shirt E-Commerce Site with Next.js 14, Google Apps Script, and AWS CloudFront

What Was Done

Deployed a full-stack t-shirt e-commerce application for 86dfrom.com, integrating Printful's print-on-demand API with a Next.js 14 storefront, Google Apps Script backend, Stripe payments, and AWS infrastructure. The project involved setting up multi-domain DNS routing, CloudFront distribution caching, S3 static hosting, and a hybrid architecture combining serverless Google Apps Script with modern Node.js APIs.

Architecture Overview

The 86dfrom project uses a three-tier architecture:

  • Frontend: Next.js 14 app with 5 compiled routes (index, product detail, cart, checkout, success pages) deployed to Vercel
  • Order Backend: Google Apps Script deployed as a web app, handling Printful order creation and Google Sheets logging
  • Static Assets: S3 bucket (86dfrom.com) with CloudFront distribution for CDN caching, supporting both primary domain and legacy redirect domain (86from.com)
  • Payment Gateway: Stripe integration with webhook endpoint at /api/webhook for order status updates

File Structure and Deployment Layout

The project was organized with clear separation of concerns:

~/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html          (Marketing landing page)
│   └── success.html        (Post-purchase confirmation)
├── gas/
│   ├── Code.gs             (Google Apps Script functions)
│   ├── appsscript.json     (GAS manifest, API scopes, version)
│   └── .clasp.json         (Deployment config for clasp CLI)
├── scripts/
│   ├── deploy.sh           (S3 + CloudFront invalidation script)
│   └── get-printful-variants.js (Node.js utility to fetch variant IDs)
└── .env.local              (Local secrets: Printful API key, Stripe keys)

The .env.local file follows the pattern:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_SECRET_KEY=sk_...
PRINTFUL_API_KEY=...
GAS_WEB_APP_URL=https://script.google.com/macros/d/{scriptId}/userweb

Infrastructure: AWS and DNS Setup

S3 Bucket Configuration

Created S3 bucket named 86dfrom.com with static website hosting enabled. The bucket policy allows public read access to all objects, essential for CloudFront origin access:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::86dfrom.com/*"
    }
  ]
}

CloudFront Distribution for Primary Domain

Deployed CloudFront distribution with the following configuration:

  • Origin: 86dfrom.com.s3.amazonaws.com (S3 static website endpoint)
  • Default Root Object: index.html (serves landing page at domain root)
  • SSL/TLS Certificate: AWS Certificate Manager (ACM) certificate for 86dfrom.com
  • Cache Behaviors: HTML files cached for 3600 seconds, assets for 86400 seconds
  • Custom Error Response: 404 errors routed to index.html for client-side routing support

Redirect Domain (86from.com) via CloudFront Function

For the legacy domain 86from.com, created a CloudFront Function (viewer request) that redirects all traffic to the primary domain:

function handler(event) {
  var request = event.request;
  var host = request.headers.host.value;
  
  if (host === '86from.com' || host === 'www.86from.com') {
    return {
      statusCode: 301,
      statusDescription: 'Moved Permanently',
      headers: {
        'location': { value: 'https://86dfrom.com' + request.uri }
      }
    };
  }
  return request;
}

This function is associated with the CloudFront distribution for 86from.com, ensuring SEO-friendly 301 redirects and DNS consolidation.

Route53 DNS Configuration

Set up Route53 hosted zone for 86dfrom.com with the following records:

  • A record: 86dfrom.com → CloudFront distribution alias (evaluated target health disabled)
  • A record: www.86dfrom.com → same CloudFront distribution
  • A record: 86from.com → separate CloudFront redirect distribution
  • ACM validation CNAMEs: DNS records for certificate authority validation (temporary, auto-removed after validation)

Deployment Pipeline

Static Site Deployment (scripts/deploy.sh)

Bash script automates S3 upload and CloudFront cache invalidation:

#!/bin/bash
aws s3 sync ./site s3://86dfrom.com --delete
aws cloudfront create-invalidation --distribution-id {DIST_ID} --paths "/*"

The --delete flag removes files from S3 that don't exist locally, maintaining a clean state. CloudFront invalidation with /* forces all edge caches to refresh, ensuring users see latest content within seconds.

Next.js Vercel Deployment

The Next.js application (routes at /, /product, /cart, /checkout, /success) builds cleanly with no errors:

npm run build
# Verifies all routes compile successfully
# Runs with: next build (from next.config.js)

Deployed to Vercel production with environment variables configured in Vercel project settings:

npx vercel@latest --prod \
  --env NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_... \
  --env STRIPE_SECRET_KEY=sk_... \
  --env PRINTFUL_API_KEY=...

Google Apps Script Deployment

The GAS backend is deployed using the clasp CLI (Google Apps Script CLI). The .clasp.json file maps the local project to a specific Google Apps Script