Building a Printful-Integrated T-Shirt E-Commerce Site with Next.js 14, Stripe, and AWS CloudFront
This post documents the technical implementation of 86dfrom.com, a print-on-demand t-shirt storefront built on Next.js 14 with Printful API integration, Stripe payment processing, and AWS CloudFront distribution. The project demonstrates a modern serverless e-commerce architecture pattern suitable for small-to-medium merchandise businesses.
Project Architecture Overview
The site follows a JAMstack-adjacent pattern with a Next.js 14 application serving as both the frontend and backend API layer. The architecture splits across three primary concerns:
- Frontend Layer: Server-rendered HTML via Next.js with client-side interactivity for variant selection and checkout
- Backend API Layer: Next.js API routes (
/app/api/*) handling Printful product sync, order creation, and Stripe webhook processing - Infrastructure Layer: Vercel for application hosting with AWS S3 + CloudFront for static asset distribution and DNS via Route53
Initial Build Verification and Clean Compilation
The Next.js 14 build was verified clean with all five primary routes compiling without errors:
$ npm run build
# Verifies:
# - /app/page.tsx (homepage with product grid)
# - /app/api/printful/variants (variant data endpoint)
# - /app/api/orders/create (order creation)
# - /app/api/webhook (Stripe webhook receiver)
# - /app/success (post-purchase confirmation page)
This validation ensures no build-time issues before credential configuration and deployment.
Project Structure and File Organization
The development workspace at /Users/cb/Desktop/86dfrom mirrors the production repository structure at ~/Documents/repos/sites/86dfrom.com:
86dfrom.com/
├── site/
│ ├── index.html (static marketing/fallback)
│ └── success.html (post-purchase landing)
├── gas/
│ ├── Code.gs (Google Apps Script webhook handler)
│ └── appsscript.json (GAS project config)
├── scripts/
│ └── deploy.sh (S3 + CloudFront deployment)
├── app/
│ ├── page.tsx
│ ├── layout.tsx
│ └── api/
│ ├── printful/variants.ts
│ ├── orders/create.ts
│ └── webhook.ts
├── .env.local (credentials — not in repo)
├── .env.example (template)
└── next.config.js
Printful API Integration Strategy
Printful serves as the print fulfillment provider. The integration requires:
- API Key Configuration: Store Printful API token in
.env.localasNEXT_PUBLIC_PRINTFUL_API_KEY(note: "NEXT_PUBLIC" prefix exposes this to the browser, acceptable since Printful API is read-only for public operations) - Variant Population: A script at
scripts/get-printful-variants.jsfetches variant IDs from the Printful store (86Store under the "Hello Dangerous" account) and returns the 5 black Bella+Canvas 3001 SKU variant IDs - Data Storage: Variant IDs are hardcoded in the
/app/api/printful/variants.tsendpoint, which the frontend queries to populate the product selector
The variant fetch uses the Printful REST API endpoint GET /api/v1/products and filters for black colorways of the Bella+Canvas 3001 unisex tee, excluding heather and specialty finishes.
Environment Configuration
The .env.local file (gitignored) contains three categories of credentials:
# Printful Integration
NEXT_PUBLIC_PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
NEXT_PUBLIC_PRINTFUL_STORE_ID=86Store
# Stripe Payment Processing
STRIPE_SECRET_KEY=sk_live_... # or sk_test_... for staging
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_live_... # frontend-safe
# Webhook Signing (populated after Stripe dashboard config)
STRIPE_WEBHOOK_SECRET=whsec_...
The NEXT_PUBLIC_* prefix indicates values safe for browser exposure (public keys, non-sensitive IDs). The secret key and webhook secret remain server-side only.
API Routes and Request Handling
/app/api/printful/variants.ts returns hardcoded variant IDs and product metadata:
// GET /api/printful/variants
// Response: {
// variants: [
// { id: 4016, size: "XS", color: "Black" },
// { id: 4017, size: "S", color: "Black" },
// ...
// ]
// }
/app/api/orders/create.ts accepts a POST with selected variant and quantity, constructs a Printful order object, and submits it to Printful's POST /api/v1/orders endpoint. The response includes an order ID, which is then passed to Stripe for payment.
/app/api/webhook.ts receives signed POST requests from Stripe when payment succeeds or fails. The handler verifies the signature using the webhook secret, updates order status in a persistent store (initially in-memory, expandable to Firestore or DynamoDB), and can trigger fulfillment confirmation with Printful.
Stripe Integration and Payment Flow
The checkout flow follows this sequence:
- User selects size/color from variant dropdown and clicks "Buy Now"
- Frontend POST to
/api/orders/createwith variant ID and quantity - Backend creates a Printful order, receives order ID
- Backend creates a Stripe CheckoutSession with the Printful order ID stored in
metadata - Frontend redirects to Stripe Checkout hosted page
- After payment, Stripe sends webhook to
/api/webhook - Webhook verifies signature and updates order status, triggering print fulfillment
The webhook secret (whsec_*) is configured in the Stripe Dashboard under Developers → Webhooks, pointing to https://86dfrom.com/api/webhook. This secret is used server-side only, via the stripe.webhooks.constructEvent() method.
Static Site and Fallback Architecture
In addition to the Next.js application, static HTML files are maintained in the site/ directory:
site/index.html— Standalone HTML homepage (used as fallback if Next.js is unavailable; also deployed to S3 for CDN caching)site/success.