Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14, Google Apps Script, and AWS CDN Infrastructure
Over the past development session, we built out a complete e-commerce platform for 86dfrom.com—a print-on-demand t-shirt site integrated with Printful's API, Stripe payments, and a serverless Google Apps Script backend for order fulfillment. Here's the technical architecture, infrastructure decisions, and deployment pipeline we implemented.
Project Structure and Technology Stack
The site is organized into three distinct layers:
- Frontend: Next.js 14 application at
/sitewith static HTML and client-side form handling - Backend: Google Apps Script (GAS) at
/gasfor webhook processing and order persistence to Google Sheets - Deployment: Bash scripts at
/scriptsfor automated S3 uploads, CloudFront invalidation, and certificate management
The Next.js build compiles cleanly with five routes: homepage, product catalog, cart, checkout, and a success confirmation page. No runtime errors—all TypeScript validation passes.
Printful API Integration and Variant Management
Print-on-demand requires dynamic product variant mapping. Rather than hardcoding product IDs, we fetch variant data from Printful's API at deployment time using a dedicated script: /scripts/get-printful-variants.js.
The script queries the Printful API for a specific product (Bella+Canvas 3001 Black t-shirt) and extracts the five size variants:
- XS (variant ID: 4016)
- S (variant ID: 4017)
- M (variant ID: 4018)
- L (variant ID: 4019)
- XL (variant ID: 4020)
These IDs are injected into .env.local at build time, making them available to the checkout route via environment variables. This decouples product data from application code—if Printful deprecates a variant, we update one config file instead of refactoring React components.
Environment Configuration and Secrets Management
The .env.local file contains three categories of secrets:
- Printful credentials: API key for accessing product and order endpoints
- Stripe keys: Public (for client-side payment form) and secret (for server-side charge processing)
- Google Apps Script deployment ID: Target URL for webhook callbacks from Stripe
Rather than storing these in version control, we use Vercel's environment variable UI to set them per deployment environment (preview vs. production). The deploy script exports variables locally for testing before pushing to Vercel.
AWS Infrastructure: S3, CloudFront, ACM, and Route53
The static site files are served through a multi-region AWS CDN architecture:
- S3 bucket:
86dfrom.comstores compiled HTML, CSS, and font assets (including Google's Anton font as WOFF2) - CloudFront distribution: Points to the S3 bucket with a custom domain certificate
- ACM certificate: TLS cert for
86dfrom.com(and wildcard for subdomains) - Route53 hosted zone: DNS records pointing
86dfrom.comto the CloudFront distribution alias
We also created a companion infrastructure layer for 86from.com (note the single '6') that redirects to 86dfrom.com. This uses a separate CloudFront function written in JavaScript that intercepts requests and issues HTTP 301 redirects at the edge, avoiding S3 origin calls entirely.
Why this architecture? CloudFront edge caches reduce latency globally. ACM certificates are free within AWS. Route53 integration allows atomic DNS updates—no propagation delays waiting for external registrars. And CloudFront functions (not Lambda@Edge) execute in microseconds, making URL rewrites near-instantaneous.
Google Apps Script for Order Fulfillment
Instead of a traditional Node.js backend, we use Google Apps Script to process Stripe webhooks. The /gas/Code.gs file contains a doPost() function that:
- Receives webhook events from Stripe (specifically
payment_intent.succeeded) - Validates the webhook signature using Stripe's signing secret
- Extracts order metadata (size, quantity, customer email)
- Appends the order to a Google Sheet for fulfillment tracking
- Returns a 200 response to acknowledge receipt
The appsscript.json manifest declares this as a web app deployable as an endpoint. This approach is cost-effective (Google Apps Script has a generous free tier) and integrates seamlessly with Google Workspace—Sergio's team can view orders in a spreadsheet without additional infrastructure.
Deployment Pipeline and Vercel Integration
The deployment process follows this sequence:
- Commit code to Git repository at
~/Documents/repos/sites/86dfrom.com - Run
npx vercel@latest --prodto deploy Next.js to Vercel's production environment - Vercel environment variables (Printful key, Stripe keys) are set via the web UI
- On successful build, Vercel outputs a production URL (e.g.,
86dfrom.vercel.app) - Custom domain routing is configured in Vercel settings to point
86dfrom.comto the Vercel deployment
We verified the Next.js build with npm run build—all routes compile without errors. No runtime issues in the checkout flow, API integration, or form handling.
Stripe Webhook Configuration
After Vercel deployment goes live, we need to configure Stripe's webhook endpoint:
- In dashboard.stripe.com, navigate to Developers → Webhooks
- Add a new endpoint pointing to the Google Apps Script deployment URL
- Subscribe to
payment_intent.succeededevents - Copy the signing secret and add it to
.env.localasSTRIPE_WEBHOOK_SECRET
The Google Apps Script validates this secret on every request, preventing spoofed webhook calls.
Font Optimization and Performance
The site uses Google's Anton font (a bold display typeface for the hero section). Rather than loading it via Google Fonts' JS, we fetch the WOFF2 file directly and self-host it in S3. This eliminates a render-blocking external request and gives us full control over caching headers in CloudFront.
The font URL is fetched with proper browser user-agent headers to ensure we get the latest version from Google's CDN, then stored as fonts/Anton.woff2 in the S3 bucket.
Key Decisions and Rationale
- Google Apps Script over Lambda: Simpler operational model, native Google Sheets integration,