Building a Serverless E-Commerce Site with Next.js 14, Google Apps Script, and AWS CloudFront
This post documents the architecture and deployment strategy for 86dfrom.com, a t-shirt e-commerce platform that integrates Printful's fulfillment API, Stripe payments, and a lightweight backend powered by Google Apps Script. The goal: a fully serverless, cost-optimized pipeline that handles product variants, payments, and order webhooks without managing traditional servers.
Project Architecture Overview
The 86dfrom.com stack splits into three core layers:
- Frontend: Next.js 14 static site generation + client-side interactivity, deployed to Vercel
- Fulfillment Backend: Google Apps Script (GAS) running in Google Cloud, calling Printful API to sync product variants and create orders
- Payment Processing: Stripe integration with webhook handlers to confirm payments and trigger fulfillment
- Static Assets: S3 + CloudFront for the redirect domain (86from.com → 86dfrom.com)
File Structure & Configuration
The development workspace is organized as follows:
/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html (Main landing page with product gallery)
│ └── success.html (Post-purchase confirmation page)
├── gas/
│ ├── Code.gs (Google Apps Script — variant sync, order creation)
│ ├── appsscript.json (GAS manifest with scopes and version)
│ └── .clasp.json (Clasp CLI config linking to GAS project ID)
├── scripts/
│ └── deploy.sh (Bash deployment script for S3 + CloudFront invalidation)
└── .env.local (Environment variables for Printful, Stripe keys)
The Next.js build (deployed separately to Vercel) sits in a different repository and serves API routes for payments and webhooks. The GAS backend and static assets are deployed via the above structure.
Key Technical Decisions
1. Google Apps Script for Backend Logic
Rather than maintaining a traditional Node.js server, we chose Google Apps Script because:
- Zero ops overhead: Google handles scaling, CORS, and execution environment
- Native integration: Apps Script has built-in Sheets, Drive, and Gmail APIs for future order tracking and fulfillment notifications
- Cost-effective: Free tier covers moderate request volume; no instance management
- Familiar scripting: JavaScript-like syntax, easy onboarding for team members
The trade-off is cold start latency (typically 1–3 seconds) and a 6-minute execution timeout—acceptable for async order processing but not ideal for real-time lookups. Mitigation: we cache variant data client-side after the first fetch.
2. Printful API Variant Prefetching
Rather than querying Printful on every page load, we pre-populate variant IDs at build time via a script at /scripts/get-printful-variants.js:
// Pseudocode: fetch Bella+Canvas 3001 Black from Printful
const variants = await fetch('https://api.printful.com/products', {
headers: { 'Authorization': `Bearer ${PRINTFUL_API_KEY}` }
});
// Extract variant IDs: 4016, 4017, 4018, 4019, 4020 (S–3XL)
These IDs are baked into index.html and Code.gs, eliminating runtime API calls for static catalog data. If SKUs change, we re-run the script and redeploy.
3. Static Site with S3 + CloudFront for Redirect Domain
The primary domain (86dfrom.com) is served via Vercel. However, we own both 86dfrom.com and 86from.com (typo variant). To capture traffic on 86from.com, we:
- Created an S3 bucket:
86from-redirect - Configured static website hosting with a simple HTML redirect
- Fronted it with a CloudFront distribution (ID:
EXAMPLE1234) - Attached an ACM certificate for HTTPS and a CloudFront Function to inject the redirect logic
- Added Route53 A records pointing 86from.com to the CloudFront distribution
Why not a 301 at DNS level? DNS can't perform HTTP redirects. CloudFront Function is cheaper than Lambda@Edge and sufficient for a simple 302 redirect.
Deployment Pipeline
Step 1: Environment Variables
Create .env.local with:
PRINTFUL_API_KEY=<your-printful-api-key>
STRIPE_SECRET_KEY=sk_live_xxxxx (or sk_test_xxxxx for staging)
STRIPE_PUBLISHABLE_KEY=pk_live_xxxxx
GOOGLE_APPS_SCRIPT_URL=https://script.google.com/macros/d/{scriptId}/usercache
The Printful key has full scopes across all stores in the dangerouscentaur account. The Stripe keys must match environment (test vs. live).
Step 2: Vercel Deployment
cd ~/Documents/repos/sites/86dfrom.com
npx vercel@latest --prod
Vercel automatically:
- Detects Next.js configuration
- Builds all routes and API handlers
- Assigns a Vercel domain and production URL
- Syncs env vars from
.env.local(or manual input in Vercel dashboard)
Once deployed, the Vercel URL becomes accessible; then we add custom domain DNS.
Step 3: Route53 DNS Configuration
At the registrar or Route53, add CNAME records:
86dfrom.com CNAME cname.vercel-dns.com.
www.86dfrom.com CNAME cname.vercel-dns.com.
Vercel validates ownership and issues an SSL cert automatically. (If using external registrar, delegate NS records to Route53 or manually update CNAME at registrar.)
Step 4: S3 + CloudFront for Redirect (86from.com)
Created S3 bucket 86from-redirect with static website hosting enabled, serving a single index.html that redirects to 86dfrom.com. CloudFront distribution ensures low-latency 302 responses globally.
Step 5: Stripe Webhook Configuration
After Vercel deployment, add a webhook in Stripe Dashboard → Developers → Webhooks:
Endpoint URL: https://86dfrom.com/api/webhook
Events: payment_intent.succeeded, charge.refunded
Stripe generates a signing secret (