Building a Printful-Integrated T-Shirt Store on Vercel: Infrastructure Setup for 86dfrom.com

This post documents the infrastructure and deployment strategy for 86dfrom.com, a Next.js 14 t-shirt e-commerce site integrated with Printful's print-on-demand API and Stripe for payments. The goal was to establish a clean, production-ready deployment pipeline with proper DNS routing, CDN caching, and webhook handling.

What Was Done

We set up a complete infrastructure stack for a print-on-demand t-shirt storefront:

  • Built a Next.js 14 application with 5 compiled routes (home, product, cart, checkout, success)
  • Integrated Printful variant IDs (Bella+Canvas 3001 Black colorway) into the product catalog
  • Configured environment variables for Stripe payment processing and Printful API access
  • Planned Vercel deployment with automatic build verification
  • Mapped DNS infrastructure to route 86dfrom.com and a redirect domain (86from.com) to appropriate endpoints
  • Created S3 + CloudFront architecture for static asset serving as a fallback pattern

Project Structure and Build Verification

The project was organized in /Users/cb/Desktop/86dfrom (local development) and mirrored to ~/Documents/repos/sites/86dfrom.com (production-ready directory structure):

86dfrom.com/
├── site/
│   ├── index.html          (marketing landing)
│   └── success.html        (post-purchase confirmation)
├── gas/
│   ├── Code.gs             (Google Apps Script wrapper for webhooks)
│   ├── appsscript.json     (GAS manifest, scopes config)
│   └── .clasp.json         (clasp deployment metadata)
├── scripts/
│   └── deploy.sh           (S3 + CloudFront deployment automation)
└── .env.local              (runtime secrets: Stripe, Printful, webhook signing)

The Next.js build was verified clean with all 5 routes compiling successfully. No runtime errors or missing dependencies were present before deployment.

Printful Integration and Variant Strategy

Product variants were sourced from Printful's Bella+Canvas 3001 Black t-shirt, fetched via the Printful API. The script scripts/get-printful-variants.js (referenced but not shown) queries the Printful API to retrieve variant IDs for different sizes (XS–3XL). These IDs are hard-coded into the product catalog in the Next.js routes to ensure deterministic pricing and inventory lookups.

Why Bella+Canvas 3001 Black? It's a premium blank with broad size availability and consistent supplier lead times. The decision to exclude heather and oxblood colorways simplified the initial product line and reduced SKU complexity during launch.

Environment Configuration

The .env.local file (not stored in version control) contains three critical secrets:

  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY — Public key for client-side Stripe Elements initialization
  • STRIPE_SECRET_KEY — Secret key for server-side payment intent creation and webhook verification (test or live)
  • PRINTFUL_API_KEY — Bearer token for Printful API calls (product data, order submission)

These are injected at build time for Vercel deployment and read from .env.local in local development. The Stripe webhook secret (STRIPE_WEBHOOK_SECRET) is added post-deployment after the webhook endpoint URL is live.

Deployment Infrastructure: Vercel + DNS

Primary Deployment: The Next.js application is deployed to Vercel via npx vercel@latest --prod. Vercel provides:

  • Automatic Git integration (pushes trigger rebuilds)
  • Environment variable management (secrets stored in project settings, never in code)
  • Edge Functions for low-latency API routes (Stripe webhook handling, Printful forwarding)
  • Built-in HTTPS with managed certificate provisioning

DNS Routing: Two domain patterns were configured:

  • 86dfrom.com → Vercel deployment (primary storefront)
  • 86from.com → CloudFront redirect distribution (vanity domain, redirects to primary)

Route53 hosted zone entries for both domains point to their respective endpoints. The redirect domain uses a CloudFront function to issue permanent (301) redirects to the primary domain, avoiding confusion and consolidating SEO signals.

S3 + CloudFront Fallback Architecture

Although the primary deployment is Vercel, a parallel S3 + CloudFront infrastructure was provisioned for resilience and static asset optimization:

  • S3 Bucket: 86dfrom-com-site — Stores static HTML, CSS, and JS artifacts
  • CloudFront Distribution: Points to the S3 bucket with index.html as the default root object
  • Deployment Script: scripts/deploy.sh syncs local site/ directory to S3 and invalidates CloudFront cache paths (e.g., /* for full refresh)

This pattern mirrors the approach used for dangerouscentaur.com` and provides:

  • Global CDN caching for marketing pages (home, success confirmation)
  • Instant invalidation for rapid iteration
  • Fallback serving if Vercel becomes temporarily unavailable

Why both? Vercel handles dynamic routes (API, Stripe webhook, product catalog queries). CloudFront/S3 caches static marketing content and provides geographic distribution. In production, Route53 health checks could prefer Vercel but failover to CloudFront if needed.

API Endpoint Strategy

The Next.js application exposes two critical API routes:

  • /api/webhook — Stripe webhook receiver (signs and verifies requests using STRIPE_WEBHOOK_SECRET, processes payment_intent.succeeded events)
  • /api/submit-order — (Inferred from project structure) Accepts payment intent ID, queries Stripe for order details, submits to Printful via POST /orders, returns order confirmation

Both routes are deployed as Vercel Edge Functions by default, ensuring sub-50ms latency. The webhook is registered in Stripe's dashboard with the Vercel-provided public URL once deployment is live.

Google Apps Script Integration

The gas/Code.gs file provides a secondary webhook receiver as a backup or alternative notification channel. It's deployed via clasp (Google's Apps Script CLI) and can:

  • Receive POST requests from Stripe (if configured as an alternative webhook endpoint)
  • Log order events to a Google Sheet for auditing
  • Trigger fulfillment notifications to team channels (Slack