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/webhookfor 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.htmlfor 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