Building a Print-on-Demand T-Shirt Store with Next.js 14, Printful, and Stripe: Infrastructure & Deployment Strategy
This post documents the complete technical setup for 86dfrom.com, a print-on-demand (POD) t-shirt storefront built on a modern serverless stack. We'll walk through the architecture decisions, infrastructure provisioning on AWS/CloudFront, and the integration patterns that connect a Next.js frontend to Printful's catalog and Stripe's payment processing.
Project Overview & Architecture
The 86dfrom project is a Next.js 14 application designed to:
- Display a curated t-shirt product catalog (sourcing designs and variants from Printful)
- Accept orders via Stripe payment forms
- Route orders to Printful for print, pack, and ship
- Handle webhooks from both Printful (order status) and Stripe (payment events)
The application lives at /Users/cb/Documents/repos/sites/86dfrom.com with the following structure:
86dfrom.com/
├── site/ # Static HTML & client assets
│ ├── index.html # Main product page
│ └── success.html # Post-purchase confirmation
├── gas/ # Google Apps Script (optional backup)
│ ├── Code.gs
│ └── appsscript.json
└── scripts/
├── deploy.sh # S3 + CloudFront deployment
└── get-printful-variants.js # Variant ID fetcher
The Next.js application itself compiles cleanly with all 5 core routes (index, product, checkout, webhook handlers for Printful and Stripe) without errors.
Infrastructure Setup on AWS
S3 & CloudFront Distribution
Following the pattern established by sibling projects in the dangerouscentaur ecosystem (e.g., chuckladd.com), we provisioned:
- S3 Bucket:
86dfrom.comin us-east-1 (required for CloudFront origin) - Bucket Policy: Restricts direct public access; all traffic flows through CloudFront distribution
- CloudFront Distribution: Caches static assets (HTML, CSS, JS, images) at edge locations globally
- ACM Certificate: Issued for both
86dfrom.comandwww.86dfrom.comwith DNS validation via Route53
Why this approach? CloudFront provides:
- Global edge caching, reducing latency for users worldwide
- DDoS protection via AWS Shield Standard
- SSL/TLS enforcement at the edge
- Lambda@Edge capability for future custom request/response logic (e.g., redirects, header injection)
The S3 bucket itself is not publicly accessible; the bucket policy restricts GET/HEAD operations to the CloudFront Origin Access Identity (OAI), preventing direct S3 URL leakage.
DNS & Route53 Configuration
Both 86dfrom.com and the redirect domain 86from.com (note: missing 'd') are managed in Route53:
- 86dfrom.com: A-record (alias) pointing to the primary CloudFront distribution
- 86from.com: A-record pointing to a separate CloudFront distribution configured with a CloudFront Function that redirects all traffic to
https://86dfrom.com
Why a separate redirect distribution? CloudFront Functions (not Lambda@Edge, which adds cold-start latency) are ideal for simple HTTP redirects. They execute in microseconds at the edge, providing instant 301 redirects before the user's browser ever hits the origin S3 bucket.
The redirect function is deployed as:
// CloudFront Function (86from.com → 86dfrom.com)
function handler(event) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': { value: 'https://86dfrom.com' + event.request.uri }
}
};
}
Printful Integration
Printful serves as the print-on-demand fulfillment partner. The integration requires:
- API Credentials: A store-level API token (generated via printful.com → Dashboard → Store Settings → API) with full scope access
- Variant Mapping: Script at
scripts/get-printful-variants.jsfetches available product variants from Printful and extracts variant IDs - Product Data: The store carries Bella+Canvas 3001 black t-shirts in sizes XS–3XL. Variant IDs are hardcoded in the checkout flow to ensure correct SKU mapping when orders are placed
The variant-fetching script makes authenticated GET requests to the Printful API (e.g., https://api.printful.com/products) and extracts the relevant IDs for the product you want to sell. These IDs are then embedded in .env.local so the checkout route knows exactly which Printful variant ID corresponds to each size/color combination.
Stripe Payment Processing
Payment flows through Stripe, with two key credentials required:
- Publishable Key: Embedded in the client-side form (can be exposed, used for Stripe.js initialization)
- Secret Key: Stored server-side in
.env.local, used for token creation, charge processing, and webhook verification
API Routes (Next.js):
/api/create-payment-intent— Creates a Stripe PaymentIntent server-side, returns client secret to frontend/api/webhook— Receives and verifies Stripe webhook signatures, processespayment_intent.succeededevents
The webhook secret (format: whsec_...) is obtained after Vercel deployment and webhook endpoint registration at dashboard.stripe.com → Developers → Webhooks. This secret is used to verify that incoming webhook payloads are genuinely from Stripe, not forged requests.
Environment Configuration & Secrets Management
The project uses a .env.local file (git-ignored) to store credentials. Template:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=...
PRINTFUL_STORE_ID=...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020
Additionally, Vercel's environment variable UI is used to persist these secrets in production, ensuring the deployed application has access to credentials without committing them to the repository.
Deployment Pipeline
To S