Building a Printful + Stripe T-Shirt Site on Vercel: Infrastructure & Deployment Strategy for 86dfrom.com
This post documents the end-to-end infrastructure and deployment setup for a Next.js 14 e-commerce site that integrates Printful for print-on-demand fulfillment and Stripe for payment processing. The project, hosted at 86dfrom.com, demonstrates a modern serverless architecture pattern that separates concerns between frontend hosting, backend API routes, and third-party service integrations.
Project Architecture Overview
The 86dfrom.com project is structured as a monorepo with distinct layers:
- Frontend: Next.js 14 app deployed to Vercel (site/ directory)
- Backend: API routes at /api/* (variant fetching, checkout, webhooks)
- Google Apps Script: Optional GAS integration for secondary workflows (gas/ directory)
- Deployment scripts: Bash automation for S3/CloudFront deployments (scripts/ directory)
The decision to use Vercel for hosting rather than S3 + CloudFront (as with other properties in the portfolio) was driven by the need for serverless functions to handle Stripe webhooks and real-time Printful API calls. Static hosting wouldn't support the dynamic checkout flow required for e-commerce.
Directory Structure & File Organization
Project files are maintained in two canonical locations during active development:
/Users/cb/Desktop/86dfrom/
├── site/
│ ├── index.html (Single-page app or static entry)
│ └── success.html (Order confirmation page)
├── gas/
│ ├── Code.gs (Google Apps Script functions)
│ └── appsscript.json (GAS manifest)
├── scripts/
│ └── deploy.sh (S3/CloudFront deployment)
└── .env.local (Local env vars, NOT committed)
~/Documents/repos/sites/86dfrom.com/ (Production git repo mirror)
└── [same structure]
The Desktop location serves as the active working directory, while the repos/sites/ path is the canonical production source for version control. This dual-path approach allows rapid iteration without strict git discipline during development, then synchronization to the production repo once changes are validated.
Next.js Build & Route Structure
The Next.js 14 build compiles cleanly with 5 defined routes:
/api/variants– Fetches Printful product variant IDs (Black t-shirt variants 4016–4020)/api/checkout– Creates Stripe checkout session, passes Printful order data/api/webhook– Handles Stripecharge.succeededevents, triggers fulfillment/– Homepage with product gallery and add-to-cart/success– Post-purchase confirmation page
The variant route is particularly critical: it encodes the business logic that maps to specific Printful SKUs. During development, we fetched the Bella+Canvas 3001 Black variant IDs directly from the Printful API using the provided store credentials. Rather than hardcoding these IDs, they're queried at build time or cached in environment variables to support future product updates without code changes.
Environment Variables & Secrets Management
The .env.local file (git-ignored) contains three categories of secrets:
- Printful credentials: Store ID and API key for variant lookups and order creation
- Stripe live keys: Public key (embedded in frontend) and secret key (server-side only)
- Webhook secret: Stripe webhook signing key (issued after setting up webhook endpoint at /api/webhook)
These are manually seeded locally, then replicated to Vercel's environment variable dashboard during deployment. The Vercel CLI syncs them automatically when you run npx vercel@latest --prod if the .env.local file exists in the project root.
Deployment Pipeline: Vercel & DNS Configuration
Vercel deployment follows this sequence:
- Ensure
.env.localis populated with Stripe and Printful credentials - Run
npx vercel@latest --prodfrom project root to deploy to production - Vercel auto-generates a deployment URL and assigns a preview deployment
- Set DNS CNAME record at registrar:
86dfrom.com CNAME cname.vercel-dns.com - Configure Vercel to recognize the custom domain in project settings
- Vercel provisions an HTTPS certificate via Let's Encrypt automatically
The decision to use Vercel's native DNS integration over manual SSL/CloudFront reduces operational overhead. Vercel handles certificate renewal, global CDN distribution, and serverless function scaling transparently. This is superior to the S3 + CloudFront pattern used for static sites in the portfolio (like chuckladd.com) because it collapses multiple services into one managed platform.
Stripe Webhook Integration
The /api/webhook route is where payment and fulfillment orchestration occurs. When a customer completes checkout:
- Stripe sends a POST to
https://86dfrom.com/api/webhookwith event typecharge.succeeded - The webhook handler verifies the signature using the webhook secret from
.env.local - On verification success, the handler extracts order metadata and calls the Printful API to create a print order
- Printful assigns a fulfillment ID and begins production
- Database or email log records the transaction (implementation details depend on data persistence choice)
This pattern decouples checkout (synchronous Stripe API call) from fulfillment (asynchronous webhook), ensuring that payment confirmation and physical order creation don't fail together.
Printful API Integration
The Printful API key provided has full scopes for the 86Store account. Two main integration points:
- Variant lookup:
GET /api/products/{product_id}/variantsreturns available SKUs with pricing and inventory - Order creation:
POST /api/ordersaccepts customer address, product variant, and shipping preference; returns a fulfillment ID
The variant IDs (4016–4020 for Black only) are sourced from the Bella+Canvas 3001 product in Printful's catalog. The decision to restrict to Black variants rather than include heather or oxblood options simplifies the initial product lineup—these can be added later by querying additional variant IDs and extending the frontend product selector.
Deployment Automation & Version Control
The scripts/deploy.sh script provides a fallback deployment method for static content, though for this project, Vercel's CLI is preferred. The script pattern mirrors the S3 + CloudFront pattern used elsewhere in the infrastructure:
#!/bin/bash
# Example static deployment (for reference)
aws s3 sync site/ s3://86dfrom-com-prod/
aws cloudfront create-invalidation --distribution-id E_XXXXX