Building a Printful-Integrated T-Shirt E-Commerce Site: Next.js 14, Stripe, and AWS Infrastructure
Overview
This post documents the complete infrastructure and application setup for 86dfrom.com, a Printful-integrated t-shirt storefront built on Next.js 14. The project demonstrates a modern serverless architecture pattern combining edge-deployed Next.js applications with AWS static hosting, API gateway abstractions, and payment processing through Stripe.
What Was Built
The 86dfrom.com project is a full-stack e-commerce application with the following components:
- Frontend: Next.js 14 application with server components and API routes
- Backend: Next.js API routes interfacing with Printful and Stripe APIs
- Payment Processing: Stripe integration with webhook handlers
- Product Variants: Dynamic variant fetching from Printful's Bella+Canvas 3001 Black t-shirt
- Infrastructure: Multi-CDN deployment with Vercel for dynamic content and S3/CloudFront for static assets
Project Structure
The codebase follows a standard Next.js 14 monolithic structure:
~/Documents/repos/sites/86dfrom.com/
├── site/ # Static HTML export (backup)
│ ├── index.html
│ └── success.html
├── gas/ # Google Apps Script (form submission handler)
│ ├── Code.gs
│ └── appsscript.json
├── scripts/
│ ├── deploy.sh # CloudFront invalidation script
│ └── get-printful-variants.js # Variant ID fetcher
├── .env.local # Environment variables (gitignored)
└── .clasp.json # Google Apps Script project config
The main Next.js application directory structure (inferred from build process) contains:
/app— App Router with pages and API routes/app/page.tsx— Homepage with product display/app/api/variants— Endpoint returning Printful variant IDs/app/api/checkout— Stripe session creation endpoint/app/api/webhook— Stripe webhook receiver for order confirmation/public— Static assets (fonts, images)
Environment Configuration
The .env.local file centralizes all external service credentials:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
PRINTFUL_STORE_ID=86Store
Why this structure: Environment variables separate secrets from code and allow environment-specific configuration. The NEXT_PUBLIC_ prefix marks variables safe to expose to the browser (publishable Stripe key only). All secret keys remain server-side only.
Printful Integration
The application fetches dynamic variant data from Printful's API. The Bella+Canvas 3001 Black t-shirt has five size variants (XS through XL) with consistent pricing. The scripts/get-printful-variants.js script performs an initial discovery:
node scripts/get-printful-variants.js
This script:
- Authenticates with the Printful API using the token in
PRINTFUL_API_KEY - Lists all products in the 86Store
- Extracts the Bella+Canvas 3001 product ID
- Maps each size variant to a Printful internal variant ID
- Outputs variant IDs for hardcoding or dynamic retrieval
The /app/api/variants endpoint caches these mappings and serves them to the frontend, allowing the product selector to display real-time pricing from Printful without exposing the API key to clients.
Stripe Payment Flow
The checkout flow follows Stripe's recommended pattern:
- User selects size and quantity on the homepage
- JavaScript submits a POST to
/app/api/checkout - The backend creates a Stripe Checkout Session with line items and metadata
- The session ID is returned and redirected to Stripe's hosted checkout
- Upon completion, Stripe POSTs to
/app/api/webhook - The webhook handler validates the signature and updates order status
Why Stripe Checkout: Hosted checkout abstracts PCI compliance from our application. Stripe handles card tokenization, fraud detection, and encryption. We only ever receive validated, confirmed payment events.
AWS Infrastructure
S3 Bucket: 86dfrom.com (us-east-1)
Configured as a static website host with the following policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::86dfrom.com/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/CF_DIST_ID"
}
}
}
]
}
Why S3 + CloudFront: Separates static asset hosting from dynamic application logic. S3 provides cheap, reliable object storage; CloudFront adds a global CDN layer with edge caching, DDoS protection, and geo-routing. This pattern reduces load on the main application and improves time-to-first-byte for visitors worldwide.
CloudFront Distributions:
- Primary (86dfrom.com): Points to S3 origin, caches HTML/CSS/JS with 1-year versioning headers
- Redirect (86from.com → 86dfrom.com): Uses CloudFront Functions to perform domain rewriting at the edge
The redirect function (deployed via Lambda@Edge integration) intercepts requests to the typo domain and rewrites them:
function handler(event) {
return {
statusCode: 301,
headers: {
'location': { value: 'https://86dfrom.com' + event.request.uri }
}
};
}
Why edge rewriting: Handles domain aliases (common typos, alternate branding) at CloudFront's edge locations, eliminating extra hops. The redirect is cached, so repeated requests from the same visitor don't re-evaluate the function.
DNS Configuration (Route53)
Both domains resolve via Route53 hosted zone records: