Building a Printful-Integrated T-Shirt Store on Vercel: Infrastructure Setup for 86dfrom.com
This post documents the infrastructure and deployment pipeline built for 86dfrom.com, a Next.js 14-based print-on-demand t-shirt storefront integrated with Printful and Stripe. The project demonstrates a modern approach to serverless commerce infrastructure, from local development through multi-region CDN distribution.
Architecture Overview
The 86dfrom.com stack consists of three core layers:
- Frontend: Next.js 14 with static and API routes deployed to Vercel
- Backend: Google Apps Script for order fulfillment workflows + serverless API routes for Printful/Stripe integration
- CDN & DNS: CloudFront distribution fronting S3 static assets, with Route53 DNS management
The decision to split between Vercel (dynamic Next.js application) and S3+CloudFront (static assets) allows for independent scaling and cache invalidation strategies appropriate to each workload type.
Project Structure & File Organization
The repository is organized into four primary directories:
~/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html # Marketing homepage
│ └── success.html # Post-purchase confirmation
├── gas/
│ ├── Code.gs # Google Apps Script backend
│ ├── appsscript.json # GAS manifest & OAuth scopes
│ └── .clasp.json # Clasp deployment config
├── scripts/
│ └── deploy.sh # S3 & CloudFront invalidation script
└── .env.local # Runtime secrets & API keys
This structure isolates concerns: static assets in site/, server logic in gas/, and deployment automation in scripts/. The .env.local file (gitignored) houses environment-specific credentials.
Printful Integration: Variant ID Population
The Next.js application requires Printful variant IDs for the core product—a Bella+Canvas 3001 Black unisex t-shirt. Rather than hardcoding these IDs, we built a script-driven discovery process:
scripts/get-printful-variants.js
This script:
- Authenticates to the Printful API using a store-specific token (scope: all endpoints, all stores)
- Queries the
/productsendpoint for the Bella+Canvas 3001 product - Extracts variant IDs for sizes XS–3XL from the Black colorway only
- Outputs a JSON map:
{ "XS": 4016, "S": 4017, ... }
Why this approach? Variant IDs are merchant-specific and stable per product. By fetching them once and storing in .env.local, we avoid API calls on every build while maintaining a single source of truth. The token scoped to the 86Store account ensures we only access this merchant's product catalog.
Environment Configuration & Secrets Management
The .env.local file follows a three-tier credential model:
# Printful
NEXT_PUBLIC_PRINTFUL_STORE_ID=86store
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
# Stripe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_... (populated post-deploy)
# Printful Variants (auto-generated)
NEXT_PUBLIC_VARIANT_XS=4016
NEXT_PUBLIC_VARIANT_S=4017
NEXT_PUBLIC_VARIANT_M=4018
NEXT_PUBLIC_VARIANT_L=4019
NEXT_PUBLIC_VARIANT_XL=4020
Key design decisions:
- NEXT_PUBLIC_* prefix: Only variant IDs and publishable keys are client-exposed. Stripe secret and Printful API keys remain server-side only.
- Store-level tokenization: The Printful token is tied to the 86Store account within the dangerouscentaur.com Printful workspace, isolating this storefront from other merchant operations.
- Webhook secret deferred: The Stripe webhook secret is generated after Vercel deployment, when the webhook endpoint URL is known.
Deployment Pipeline: Vercel Production
Deployment to Vercel is triggered via:
npx vercel@latest --prod
This command:
- Bundles the Next.js 14 application (confirmed clean compile with all 5 routes)
- Uploads to Vercel's global CDN infrastructure
- Assigns a
*.vercel.apppreview URL and production alias - Injects environment variables from
.env.local(or Vercel dashboard) into the serverless runtime
The production deployment ensures:
/(homepage) serves the landing page and product selector/api/checkoutcreates Stripe checkout sessions linked to Printful product variants/api/webhook(Stripe) processes payment completion and triggers GAS fulfillment workflows/successrenders post-purchase confirmation
DNS & CloudFront Configuration
The 86dfrom.com domain is managed via Route53 in AWS, with CloudFront serving as the primary CDN layer. The infrastructure includes:
- S3 bucket:
86dfrom.com(us-east-1) — hosts static assets and homepage fallback - CloudFront distribution: Fronts the S3 bucket with TLS termination via ACM certificate
- Route53 hosted zone: Manages DNS for 86dfrom.com, with A/AAAA alias records pointing to the CloudFront distribution
- ACM certificate: Validates ownership via DNS CNAME records in Route53, ensuring wildcard TLS support
Why this topology? Vercel handles dynamic content (Next.js API routes), while S3+CloudFront handles static fallbacks and asset delivery. The Route53 alias record (not a CNAME) to CloudFront avoids DNS apex conflicts and provides instant failover.
Google Apps Script Backend
The gas/Code.gs file implements a lightweight order fulfillment service triggered by Stripe webhook events:
function handleStripeWebhook(request) {
// Verify webhook signature
// Extract order data (customer, variant, size, address)
// Create fulfillment request to Printful API
// Log to Google Sheets for manual review
// Return 200 OK to acknowledge receipt
}