Building a Vercel + Printful + Stripe T-Shirt Commerce Site: Infrastructure and Deployment Strategy
Over the past development session, we built out a full-stack t-shirt commerce platform for 86dfrom.com—a Next.js 14 application integrated with Printful for on-demand printing, Stripe for payments, and Google Apps Script for order fulfillment automation. This post covers the infrastructure decisions, deployment patterns, and technical architecture we implemented.
Project Architecture Overview
The 86dfrom.com project is structured as a monorepo containing three distinct deployment targets:
- Frontend: Next.js 14 application deployed to Vercel (handles product browsing, cart, checkout UI)
- Backend API: Vercel serverless functions at
/api/*routes (Stripe webhook handlers, Printful variant fetching, order creation) - Fulfillment Automation: Google Apps Script (GAS) deployed as a standalone web app, handles order processing and fulfillment workflows
The file structure was organized as:
/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/ # Next.js public assets and HTML
│ ├── index.html # Landing page (generated/static)
│ └── success.html # Checkout success confirmation
├── gas/ # Google Apps Script
│ ├── Code.gs # Main fulfillment logic
│ └── appsscript.json # GAS project manifest
└── scripts/
└── deploy.sh # Automated S3 + CloudFront deployment
Build Verification and Clean Deployment
Before touching infrastructure, we verified the Next.js build was production-ready. Running the full build process confirmed all five API routes compiled without errors:
/api/variants– Fetches Printful product variants/api/checkout– Creates Stripe checkout sessions/api/webhook– Handles Stripe payment confirmations/api/orders– Creates orders in Printful/api/health– Health check for monitoring
A clean Next.js build is critical because Vercel's deployment system depends on detecting a valid package.json, next.config.js, and build artifacts in .next/. Any TypeScript or import errors would fail silently during deploy, so we always verified locally first.
Environment Configuration Strategy
The project uses a multi-environment setup with .env.local for local development and Vercel's project settings for production secrets. The key environment variables required are:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY– Client-side Stripe key (safe to expose)STRIPE_SECRET_KEY– Server-side Stripe authentication (sensitive)STRIPE_WEBHOOK_SECRET– HMAC signing verification for webhook events (sensitive)PRINTFUL_API_KEY– Printful store authentication (sensitive)
We intentionally kept the publishable key as NEXT_PUBLIC_* because Stripe's frontend SDK requires it in the browser. The secret key and webhook secret are strictly server-side and injected via Vercel's environment variable dashboard, never committed to version control.
Printful Integration and Variant Management
Rather than hardcoding product variants, we created an automated Printful API lookup script at scripts/get-printful-variants.js. This script:
- Authenticates to the Printful API using the store API token
- Fetches all available products and their variants from the 86Store
- Filters for the Bella+Canvas 3001 Black t-shirt
- Extracts variant IDs for sizes XS through 3XL
- Outputs a JSON structure ready to populate
.env.local
This approach decouples product data from code, allowing store managers to update inventory in Printful without code changes. The variant IDs (4016–4020) are fetched once during setup and stored as environment variables.
Stripe Webhook Security and Verification
The /api/webhook route implements Stripe's recommended HMAC verification pattern. When Stripe sends a payment confirmation:
- The raw request body is read as a Buffer (not parsed JSON)
- The
Stripe-Signatureheader is extracted - We compute an HMAC-SHA256 digest using the webhook secret
- The computed signature is compared (constant-time) against the header
- Only verified events trigger downstream actions (order creation, email, etc.)
This prevents replay attacks and ensures the webhook truly originated from Stripe's servers. The webhook secret is unique per environment (test vs. live) and never exposed to the client.
Vercel Deployment and DNS Configuration
We deployed the Next.js application to Vercel production using:
npx vercel@latest --prod
Vercel automatically:
- Detects the Next.js framework from
package.json - Builds the application in an isolated container
- Deploys the frontend to a global CDN (Vercel Edge Network)
- Provisions serverless functions for all
/pages/apiroutes - Assigns a default
86dfrom.vercel.appdomain
To point 86dfrom.com at Vercel, we configured DNS by adding a CNAME record at the domain registrar pointing to Vercel's edge nodes. Once DNS propagated, we added the custom domain to the Vercel project settings, which automatically provisioned an SSL certificate via Let's Encrypt.
Environment Variable Injection into Vercel
Rather than storing secrets in .env.local` (which is gitignored), we added all sensitive values to Vercel's project dashboard under Settings → Environment Variables. This ensures:
- Secrets are encrypted at rest by Vercel
- Each deployment automatically receives the correct environment for its branch (production vs. preview)
- Secrets never appear in logs or error messages
- Team members can deploy without local secret management
The Vercel dashboard provides a UI to add variables, or we can use the Vercel CLI: vercel env add STRIPE_SECRET_KEY (which prompts for the value interactively).
Google Apps Script Deployment
The gas/ directory contains a standalone Google Apps Script project for fulfillment automation. The appsscript.json manifest declares:
{
"timeZone": "America/Los_Angeles",
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets"],
"webapp": {
"access": "DOMAIN",
"execute