```html

Building a Printful + Stripe T-Shirt Store on Next.js 14: Infrastructure, API Integration, and Deployment

Over the last development session, we built out a complete e-commerce storefront for 86dfrom.com — a custom t-shirt ordering system powered by Printful's print-on-demand API, Stripe payment processing, and Next.js 14 running on Vercel. This post covers the architecture decisions, infrastructure setup, and integration patterns that make this system work.

What We Built

The 86dfrom project is a Next.js 14 application with the following structure:

  • Frontend: Five core routes compiled and verified clean: product display, variant selection, checkout, order confirmation, and success pages
  • Backend: API routes for Printful variant lookup, Stripe payment processing, and webhook handling
  • Infrastructure: Vercel for application hosting, Route53 and ACM for DNS/SSL, CloudFront for CDN and redirect logic, S3 for static asset hosting
  • Third-party APIs: Printful for product variants and order fulfillment, Stripe for payment processing

API Integration: Printful Variants and Product Data

The Printful integration starts with a critical build-time script: scripts/get-printful-variants.js. This script:

  1. Authenticates to the Printful API using a store-specific token (scoped to the "86Store" within the dangerouscentaur.com Printful account)
  2. Queries the Bella+Canvas 3001 black t-shirt product to fetch all available variant IDs (sizes and color options)
  3. Returns exactly five variant IDs (sizes XS through 2XL, black only) — we deliberately excluded heather and oxblood color variants to keep the MVP focused
  4. These variant IDs are consumed by gas/Code.gs (Google Apps Script) and embedded in frontend environment variables

The script is invoked before deployment to ensure variant data stays synchronized with Printful's catalog:

node scripts/get-printful-variants.js

This approach keeps variant configuration external to the codebase — if a variant is discontinued or a new size is added on Printful's side, the script updates without code changes.

Backend: Next.js API Routes for Payment and Fulfillment

The application exposes several API routes under app/api/:

  • /api/variants — Returns the Bella+Canvas 3001 variant IDs and pricing for the frontend product page
  • /api/checkout — Creates a Stripe Payment Intent, stores order metadata (variant ID, size, quantity), and returns a client secret for frontend payment processing
  • /api/webhook — Listens for payment_intent.succeeded events from Stripe, verifies the webhook signature, and forwards order data to Printful's order creation endpoint

The webhook handler is critical: it ensures that only verified, successfully-paid orders are sent to Printful. The implementation validates the Stripe webhook signature using the raw request body and a webhook secret stored in .env.local:

stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET)

This prevents unauthorized order creation or replay attacks.

Infrastructure: Multi-Region DNS, SSL, and CDN

The infrastructure spans multiple AWS services to achieve fast, secure, globally-distributed service:

  • Route53: Hosts the 86dfrom.com zone with two CloudFront alias records:
    • Primary A record pointing to the main application CloudFront distribution (serves the Vercel-deployed Next.js app)
    • Secondary redirect distribution for legacy/variant domain handling
  • ACM (AWS Certificate Manager): Issues and auto-renews SSL certificates for both 86dfrom.com and 86from.com (the typo variant, redirected to the canonical domain)
  • CloudFront: Two distributions:
    • Primary: Points to the Vercel deployment, caches static assets (Next.js `_next/` folder), and sets cache headers for HTML (short TTL) and JS/CSS (long TTL)
    • Redirect: Points to S3 (static HTML), uses a CloudFront Function to rewrite 86from.com/* requests to https://86dfrom.com/*
  • S3: Two buckets: one for static site hosting (redirect domain), one for backup static assets

The CloudFront Function (published to the redirect distribution) intercepts requests and returns a 301 redirect:

function handler(event) {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: {
      'location': { value: 'https://86dfrom.com' + event.request.uri }
    }
  };
}

This pattern is cleaner than S3 bucket redirect rules because it preserves request paths and works at the edge, avoiding an extra S3 round-trip.

Environment Configuration and Secrets Management

The project uses three configuration layers:

  1. .env.local (development): Git-ignored file with local Printful API key, Stripe test/live keys, and webhook secret
  2. Vercel environment variables (production): Set via the Vercel dashboard or CLI, includes the same set for production deployment
  3. Google Apps Script appsscript.json: Manifests OAuth scopes for the standalone GAS deployment (not used in the main Next.js app, but kept for legacy/admin workflows)

The .env.local file structure is:

PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
STRIPE_SECRET_KEY=sk_live_... # or sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_live_... # or pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

The webhook secret is obtained after creating the webhook endpoint in the Stripe dashboard — it's not committed to version control and must be set per environment.

Deployment Pipeline

The deployment flow is:

  1. Run npm run build locally to verify all 5 routes compile without errors
  2. Push code to git (or deploy directly to Vercel)
  3. Invoke npx vercel@latest --prod to deploy to production
  4. Vercel automatically builds the Next.js app, runs the middleware, and deploys to its global edge network
  5. Invalidate the CloudFront cache to ensure the latest version is served globally

The scripts/deploy.sh file automates the S3 + CloudFront portion for static assets (legacy pattern for the redirect domain), but the main app deployment is handled by Vercel's CLI.