Building a Print-on-Demand T-Shirt Store with Next.js 14, Printful, and Stripe: Infrastructure & Deployment
Overview
This post documents the technical architecture and deployment workflow for 86dfrom.com, a print-on-demand (POD) t-shirt e-commerce site built on Next.js 14, integrated with Printful's fulfillment API and Stripe for payments. We'll cover the full stack: local development setup, API integration patterns, S3+CloudFront static hosting for the marketing site, and Vercel deployment for the Next.js app.
What Was Done
Project Structure & Local Setup
The project lives at /Users/cb/Desktop/86dfrom with a monorepo-like structure:
site/— Static marketing HTML (index.html, success.html)gas/— Google Apps Script deployment files (Code.gs, appsscript.json)scripts/— Utility scripts for deployment and variant population- Next.js root —
app/directory with API routes and pages
The Next.js build passed clean with all 5 routes compiling successfully. This confirms the code is ready for production before any credential injection.
Printful API Integration
Printful API key was provisioned for the "Hello Dangerous" account with an 86Store sub-account. The integration requires:
- Variant ID population: A script at
scripts/get-printful-variants.jsqueries the Printful API endpointhttps://api.printful.com/store/{store_id}/productsto retrieve Bella+Canvas 3001 black t-shirt variant IDs (SKUs 4016–4020) - Environment injection: Variant IDs stored in
.env.localwith keys likeNEXT_PUBLIC_VARIANT_ID_XS,NEXT_PUBLIC_VARIANT_ID_S, etc. - API routes:
app/api/submit-order.jsandapp/api/webhook.jshandle order submission to Printful and webhook validation respectively
The Printful token has full scope across all stores, valid through 2028, and the API call pattern follows their REST specification with bearer token auth in the Authorization header.
Stripe Payment Integration
Stripe integration uses both publishable and secret keys in a standard client-server split:
- Client-side: Publishable key embedded in
.env.localasNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY - Server-side: Secret key in
.env.localasSTRIPE_SECRET_KEY, used only in API routes (app/api/webhook.js) - Webhook: Endpoint at
/api/webhookconfigured in Stripe Dashboard to validate order payments and trigger fulfillment
The webhook secret (whsec_...) will be generated after Vercel deployment and registered in both Vercel env vars and Stripe's webhook settings.
Infrastructure Architecture
Static Site Hosting (Marketing Pages)
The marketing HTML files (site/index.html, site/success.html) are deployed to AWS infrastructure:
- S3 bucket:
86dfrom.com(regional bucket for lowest-cost static hosting) - CloudFront distribution: Primary distribution fronts the S3 bucket, caches HTML/CSS/JS globally, and invalidates on deploy
- ACM certificate: Wildcard cert for
*.86dfrom.comand86dfrom.comwith DNS validation via Route53 - Route53 hosted zone: Contains A and AAAA records pointing to the CloudFront distribution alias
A second CloudFront distribution handles the legacy 86from.com domain as a redirect-only distribution using CloudFront Functions, mapping traffic to the canonical 86dfrom.com without a 301 redirect (preserving the address bar appearance).
Next.js App Deployment
The Next.js application is deployed to Vercel, which:
- Automatically builds on push to the connected Git repository
- Serves the app at
https://86dfrom.comvia Vercel's edge network - Stores all environment variables (Stripe keys, Printful API key, variant IDs) in the Vercel project settings
- Provides serverless function execution for
/api/submit-orderand/api/webhookroutes
Deployment Automation
The scripts/deploy.sh script automates the full deployment pipeline:
#!/bin/bash
# Example structure (no actual secrets shown)
export AWS_ACCESS_KEY_ID=[redacted]
export AWS_SECRET_ACCESS_KEY=[redacted]
# Build Next.js
npm run build
# Sync site/ directory to S3
aws s3 sync site/ s3://86dfrom.com --delete
# Invalidate CloudFront cache
aws cloudfront create-invalidation \
--distribution-id [CLOUDFRONT_DIST_ID] \
--paths "/*"
# Deploy to Vercel (requires VERCEL_TOKEN in environment)
npx vercel@latest --prod
Key Technical Decisions
Why S3 + CloudFront for Static Content?
While Vercel could serve static files, separating static marketing content (S3) from dynamic app logic (Vercel) provides cost efficiency and independent cache invalidation. Marketing pages rarely change; Vercel deployments don't need to invalidate them.
Why Environment Variables Over Config Files?
Variant IDs, API keys, and secret keys are injected via .env.local (local dev) and Vercel's UI (production). This prevents hardcoding sensitive data in source control and enables environment-specific configurations (test vs. live Stripe keys).
Why CloudFront Functions for Domain Redirect?
A traditional 301 redirect requires a backend. CloudFront Functions execute at edge locations with sub-millisecond latency and zero compute cost, making them ideal for simple URL rewrites. The function intercepts requests to 86from.com and internally rewrites them to 86dfrom.com.
Why Bearer Token Auth for Printful?
Printful's API uses OAuth 2.0-style bearer tokens. The token is stored server-side in Vercel env vars and injected into request headers via Authorization: Bearer ${PRINTFUL_API_KEY}, preventing exposure to the client.
Environment Variables Checklist
Before deploying, these env vars must be populated in .env.local (local) and Vercel project settings (production):