Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14 + Google Apps Script + AWS Infrastructure
What Was Done
We built a complete e-commerce storefront for 86dfrom.com—a Printful-integrated t-shirt sales site—with a modern Next.js 14 frontend, serverless backend, and AWS-hosted static fallback infrastructure. The project demonstrates a hybrid deployment pattern: Vercel for the dynamic web app, AWS S3 + CloudFront for static content delivery, and Google Apps Script for order fulfillment automation.
Architecture Overview
The site follows a multi-tier architecture:
- Frontend Layer: Next.js 14 app running on Vercel, handling product listings, variant selection, and Stripe checkout flow
- Order Processing: Google Apps Script (Google Cloud Platform) triggered via webhook, writing order data to a Google Sheet and interfacing with Printful API
- Payment: Stripe (production or test keys), with webhook validation at
/api/webhook - Product Data: Printful API for real-time variant retrieval and SKU management
- Static Backup: AWS S3 bucket (86dfrom-static-site-prod) + CloudFront distribution for resilience if the primary Vercel deployment goes down
- DNS: Route53 for 86dfrom.com and 86from.com (redirect only)
File Structure & Key Components
The project layout mirrors a standard Next.js monorepo with additional deployment automation:
~/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html (Static fallback landing page)
│ └── success.html (Order confirmation page)
├── gas/
│ ├── Code.gs (Google Apps Script main)
│ └── appsscript.json (GAS manifest & OAuth scopes)
├── scripts/
│ ├── deploy.sh (S3 + CloudFront deployment)
│ └── get-printful-variants.js (Variant ID fetcher)
└── .env.local (Runtime secrets: PRINTFUL_API_KEY, STRIPE_SECRET_KEY, etc.)
The Next.js app itself (deployed separately to Vercel) lives in the primary git repo and includes:
/app/page.tsx– Home page with product grid and variant selector/app/checkout/page.tsx– Stripe checkout integration/app/api/webhook– Stripe webhook endpoint for order capture/app/api/variants– Backend route exposing Printful variant data (populated byget-printful-variants.js)
Technical Implementation Details
Printful Integration
The scripts/get-printful-variants.js script runs during build to fetch all available product variants from the Printful store "86Store" using the provided API key. It specifically targets the Bella+Canvas 3001 Black unisex t-shirt in five sizes (XS–2XL) and writes a JSON manifest to the frontend. This decouples product metadata from code, allowing inventory or pricing changes on Printful to propagate without redeployment.
Why this pattern: Printful's variant IDs are opaque and store-specific. Hardcoding them invites bugs when inventory changes. Fetching at build time ensures the Next.js build fails loudly if the API is unreachable—catching deployment issues early.
Google Apps Script Automation
The gas/Code.gs file contains a doPost() function that listens for incoming webhook POST requests (from Stripe or manual triggers). When invoked, it:
- Validates the request signature (for Stripe webhooks)
- Extracts order metadata (customer email, size, quantity, amount)
- Appends a row to a Google Sheet for fulfillment tracking
- Calls the Printful API to create a print order, using the pre-fetched variant IDs
- Logs results and errors for debugging
The appsscript.json manifest declares OAuth scopes (spreadsheets, script.external_request) needed for Google Sheets access and outbound HTTP calls to Printful.
Why Apps Script: It's serverless, avoids cold starts, integrates natively with Google Workspace (Sheets), and costs nothing at our scale. The trade-off is limited observability compared to a Lambda function, but for a small shop, the simplicity wins.
Stripe Webhook Handling
The /api/webhook route in Next.js validates incoming Stripe events using the webhook secret, ensuring only legitimate Stripe requests trigger order creation. Once validated, it makes an authenticated POST call to the Google Apps Script endpoint, passing order details. The webhook secret itself (prefixed whsec_) is stored in Vercel's environment variables and never committed to git.
Security consideration: The webhook endpoint is publicly accessible but protected by signature verification. Without it, anyone could forge orders. Stripe's signature uses HMAC-SHA256, making forgery computationally infeasible.
AWS Infrastructure Deployment
S3 & CloudFront Setup
We created two AWS resources for static hosting:
- S3 Bucket:
86dfrom-static-site-prod– storessite/index.htmlandsite/success.html - CloudFront Distribution: ID
E1ABC2D3FG4HI(example) – caches S3 content at AWS edge locations worldwide, adds HTTPS, and handles compression
The bucket policy allows CloudFront's Origin Access Identity to read objects but denies direct public S3 access, forcing all traffic through CloudFront. This ensures:
- HTTPS-only delivery (no mixed content)
- Geo-distribution reducing latency
- DDoS protection via AWS Shield
- Cache invalidation control (via
scripts/deploy.sh)
ACM Certificates & DNS
We requested ACM (AWS Certificate Manager) certificates for both 86dfrom.com and 86from.com, with validation via DNS CNAME records added to Route53. This avoids email validation delays and provides automatic renewal before expiry.
Route53 hosted zone for 86dfrom.com now contains:
A record(alias) → CloudFront distribution (for86dfrom.comandwww.86dfrom.com)A record(alias) → Redirect CloudFront distribution (for86from.com→86dfrom.com)
The redirect distribution uses a CloudFront function (Lambda@Edge alternative) to intercept all 86from.com