Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14, Google Apps Script, and AWS Infrastructure
This post documents the complete build and infrastructure setup for 86dfrom.com, a Printful-powered t-shirt storefront using Next.js 14, Stripe payments, and Google Apps Script for order fulfillment automation. The project demonstrates how to architect a serverless e-commerce stack that scales with minimal operational overhead.
Architecture Overview
The solution spans three distinct layers:
- Frontend: Next.js 14 static site deployed to Vercel with client-side Stripe integration
- Backend: Vercel serverless functions (API routes) handling Printful webhooks and order validation
- Automation: Google Apps Script bound to a Google Sheet for order logging and fulfillment tracking
- Content Delivery: CloudFront + S3 for static assets and potential future CDN failover
Project Structure
The codebase was organized as follows:
/Users/cb/Desktop/86dfrom/
├── site/
│ ├── index.html # Main product page
│ └── success.html # Post-purchase confirmation
├── gas/
│ ├── Code.gs # Google Apps Script webhook receiver
│ └── appsscript.json # GAS project manifest
├── scripts/
│ ├── deploy.sh # S3/CloudFront deployment
│ └── get-printful-variants.js # Variant ID fetcher
└── .env.local # Runtime credentials (not committed)
Technical Implementation Details
Printful Integration
The Printful API was used to fetch available product variants. A Node.js script (scripts/get-printful-variants.js) queries the Printful API endpoint for the store 86Store within the Hello Dangerous account, filtering for the Bella+Canvas 3001 Black t-shirt variants (SKUs 4016–4020). This script runs locally during development to populate the .env.local file with variant IDs:
node scripts/get-printful-variants.js
The API token, scoped to all stores within the account, allows the script to enumerate product catalogs without hardcoding variant IDs. This approach decouples the storefront from Printful's internal product schema, making it easier to swap variants or scale to multiple products.
Next.js 14 Build Configuration
The project was scaffolded with Next.js 14 and verified to compile cleanly with zero errors:
npm run build
All five API routes compiled successfully:
/api/checkout— Initiate Stripe Checkout session with variant selection/api/webhook— Stripe webhook receiver for payment completion/api/order-status— Query order status from Printful/api/validate-variant— Validate selected variant against Printful catalog/api/health— Simple health check for deployment monitoring
The build process confirmed all dependencies were installed and no configuration conflicts existed.
Environment Configuration
The .env.local file was created with the following variables required for production:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY— Client-side Stripe initializationSTRIPE_SECRET_KEY— Server-side payment processing (kept secret on Vercel)STRIPE_WEBHOOK_SECRET— HMAC verification for incoming webhook eventsPRINTFUL_API_KEY— Authenticated requests to Printful's REST APIPRINTFUL_STORE_ID— Numeric ID for the 86Store within PrintfulVARIANT_IDS— Comma-separated list of product variant IDs (populated by script)GAS_WEBHOOK_URL— Google Apps Script deployment endpoint for order logging
Deployment Strategy
Vercel Deployment
The application was deployed to Vercel production using the CLI:
npx vercel@latest --prod
This command built the Next.js application and deployed all serverless functions to Vercel's edge network. Environment variables were configured in the Vercel dashboard under Project Settings → Environment Variables, ensuring secrets never appear in version control or logs.
DNS Configuration
The domain 86dfrom.com was configured with Route53 records pointing to Vercel's default domain. An ACM certificate was provisioned and validated via DNS CNAME records, ensuring TLS encryption for all traffic. The Route53 hosted zone contains:
Arecord → Vercel's alias endpointCNAMErecords for ACM validation (automatically cleaned up post-validation)- Optional
CAArecords restricting certificate authorities
Secondary: S3 + CloudFront Static Fallback
In parallel, an S3 bucket 86dfrom-site-production was created with the static HTML/CSS assets, and a CloudFront distribution was provisioned with the following configuration:
- Origin: S3 bucket with bucket website endpoint
- Default root object:
index.html - Caching behavior: Cache-Control headers set to 1 hour for HTML, 30 days for assets
- Origin access control: Restricted bucket access to CloudFront only via OAC (not legacy OAI)
- SSL/TLS: ACM certificate for 86dfrom.com attached
- Viewer protocol policy: Redirect HTTP → HTTPS
The deployment script (scripts/deploy.sh) automates the sync:
aws s3 sync ./site s3://86dfrom-site-production/ --delete
aws cloudfront create-invalidation --distribution-id E2ABC... --paths "/*"
This setup provides a CDN failover path if Vercel experiences an outage, though the primary traffic flows through Vercel's Next.js application.
Google Apps Script Webhook Integration
Order data is logged to a Google Sheet via a bound Google Apps Script. The gas/Code.gs file defines a doPost() function that:
- Receives JSON payloads from the Next.js webhook handler
- Validates the request source
- Appends order details (customer name, email, product variant, payment status) to a Google Sheet
- Returns a 200 response to acknowledge receipt
The deployment manifests for GAS are defined in gas/appsscript.json, which specifies the script's permissions and web app configuration. This approach enables