Building a Printful + Stripe T-Shirt Commerce Site: Next.js 14, CloudFront, and Google Apps Script Integration
This post documents the full-stack implementation of 86dfrom.com, a Printful-powered t-shirt e-commerce site built with Next.js 14, deployed to Vercel, and backed by AWS CloudFront + S3 for static assets. The project integrates Stripe payments, Google Apps Script for order fulfillment workflows, and environment-driven variant management.
Project Structure and Technology Stack
The codebase lives at /Users/cb/Documents/repos/sites/86dfrom.com/ with the following structure:
site/— Static HTML for landing/success pagesgas/— Google Apps Script for backend order processingscripts/— Deployment and utility scripts- Next.js 14 routes in
app/directory (standardpages/api/pattern)
Why this stack? Next.js 14 provides server-side API routes without a separate backend. Printful's REST API handles inventory and fulfillment. Stripe manages payments. Google Apps Script (deployed as a web app) logs orders to a shared Google Sheet, creating an auditable fulfillment queue. CloudFront + S3 cache static assets globally with cache invalidation on deploy.
Next.js Routes and Stripe Integration
The build compiles cleanly with five API routes:
/api/webhook— Stripe webhook endpoint (receivespayment_intent.succeededevents)/api/variants— Returns Printful product variant IDs from environment variables/api/orders— Accepts POST requests with customer data and redirects to Printful checkout/api/health— Health check for monitoring- Additional routes for product data and pricing
Each route validates environment variables at startup. The /api/orders endpoint constructs a Printful fulfillment request, storing the order ID in a session for webhook correlation. Stripe webhook processing happens at /api/webhook, which verifies the signature using process.env.STRIPE_WEBHOOK_SECRET and calls the Google Apps Script URL to log the order.
Environment Configuration and Build Pipeline
The project uses a .env.local file (git-ignored) populated at deployment time with:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
PRINTFUL_STORE_ID=...
GAS_WEBHOOK_URL=...
The next build command compiles all routes with TypeScript checking enabled. A clean build confirms no runtime errors in API handlers. Environment variables are injected at Vercel build time via the Vercel dashboard UI (Settings → Environment Variables), not committed to the repository.
Printful Variant Management
Printful product variant IDs are fetched via a one-time script at scripts/get-printful-variants.js:
node scripts/get-printful-variants.js
This script calls the Printful API with the store token and product ID, outputting variant IDs for Bella+Canvas 3001 in Black (SKU variants 4016–4020 covering XS–3XL). Variant IDs are stored in .env.local as comma-separated values, allowing the frontend to construct variant-specific checkout links without additional API calls.
Why pre-compute variants? Printful variant IDs are static for a given product. Caching them in environment variables reduces API calls, speeds up checkout, and makes the variant list auditable in version control (or at least documented).
Infrastructure: AWS, Route53, ACM, and CloudFront
Static assets are hosted on AWS:
- S3 Bucket:
86dfrom-site-assets— storessite/index.html,site/success.html, and public static files - CloudFront Distribution: Primary distribution for
86dfrom.com(public, caching enabled) - Route53 Hosted Zone: DNS records for
86dfrom.compoint to CloudFront - ACM Certificate: Issued for
86dfrom.com(DNS validation via Route53 CNAME records)
Redirect handling: A second CloudFront distribution (with Lambda@Edge function) handles the S3 bucket policy restricts access to the CloudFront origin identity only—no public reads. This ensures traffic flows through CloudFront's cache layer and WAF integration (if enabled later). The deployment flow is: The Why Apps Script? It's lightweight, integrates with Google Sheets (familiar to non-technical staff), and requires no additional infrastructure. The script's web app URL is stored in The 86from.com domain (typo variant), redirecting all traffic to 86dfrom.com
Deployment Pipeline
npm run buildnpx vercel@latest --prod (Vercel assigns a *.vercel.app URL and edge functions)bash scripts/deploy.shaws cloudfront create-invalidation --distribution-id E123... --paths "/*"scripts/deploy.sh uses the AWS CLI to sync site/ to s3://86dfrom-site-assets/ with cache headers for long-lived assets. HTML files are set to no-cache, ensuring users always fetch the latest index.Google Apps Script for Order Logging
gas/Code.gs script (deployed to Google Apps Script) exposes a web app endpoint that accepts POST requests with order data (customer email, name, Stripe payment ID, Printful order ID). It appends rows to a Google Sheet, creating an auditable fulfillment log.process.env.GAS_WEBHOOK_URL and called asynchronously after Stripe webhook verification.appsscript.json manifest declares OAuth scopes for Sheets access and a deployable web app.Key Decisions and Trade-offs