Building a Printful-Integrated T-Shirt Commerce Site: Infrastructure Setup for 86dfrom.com
This post documents the infrastructure and deployment pipeline for 86dfrom.com, a Next.js 14-based print-on-demand t-shirt storefront integrated with Printful's API and Stripe for payments. We'll cover the architecture decisions, S3/CloudFront setup, and the CI/CD patterns that enable rapid iteration on a statically-hosted dynamic site.
Project Architecture Overview
86dfrom.com is structured as a hybrid deployment:
- Frontend: Next.js 14 application deployed to Vercel (serverless)
- Static assets & fallback: S3 bucket (
86dfrom.com) with CloudFront distribution for edge caching - Print API integration: Printful API for inventory, variants, and fulfillment
- Payments: Stripe for checkout and webhook handling
- Domain & DNS: Route53 for authoritative DNS, ACM for TLS certificates
This hybrid approach decouples the API-driven checkout experience (Vercel) from the static marketing site (S3/CloudFront), reducing latency and infrastructure cost while maintaining the flexibility to add serverless functions as needed.
File Structure & Deployment Layout
The project lives in two canonical locations during development:
/Users/cb/Desktop/86dfrom/— working directory with source files~/Documents/repos/sites/86dfrom.com/— canonical git-backed source (synced viascripts/deploy.sh)
Directory structure:
86dfrom.com/
├── site/
│ ├── index.html (marketing homepage)
│ └── success.html (post-purchase confirmation page)
├── gas/
│ ├── Code.gs (Google Apps Script for webhook relay, if needed)
│ └── appsscript.json (GAS config & manifest)
├── scripts/
│ ├── deploy.sh (S3 sync + CloudFront invalidation)
│ └── get-printful-variants.js (fetch & cache variant IDs)
└── .env.local (credentials: Printful API key, Stripe secret, etc.)
AWS Infrastructure: S3 & CloudFront Setup
The static site is served from an S3 bucket paired with a CloudFront distribution for global edge caching and TLS termination.
S3 Bucket Configuration
Bucket name: 86dfrom.com
Configuration applied:
- Block Public Access: All disabled (public website)
- Bucket Policy: CloudFront Origin Access Identity (OAI) only — direct S3 access blocked
- Static Website Hosting: Disabled (CloudFront handles TLS and custom headers)
- Versioning: Enabled for safe deployments
The bucket policy restricts all object access to the CloudFront OAI, preventing direct HTTP requests and ensuring all traffic flows through the CDN:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity "
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::86dfrom.com/*"
}
CloudFront Distribution
Distribution ID: (retrieved during deployment, used in scripts/deploy.sh for cache invalidation)
Key settings:
- Origin:
86dfrom.com.s3.us-east-1.amazonaws.comwith OAI - Viewer Protocol Policy: Redirect HTTP → HTTPS
- TLS Certificate: AWS Certificate Manager (ACM) —
86dfrom.com(issued via DNS validation in Route53) - Default Root Object:
index.html - Cache Behaviors:
*.html— TTL 300s (5 min, for rapid iteration)*.js, *.css— TTL 31536000s (1 year, with version hashing)*.png, *.jpg, *.woff2— TTL 31536000s
- Custom Headers:
Cache-Control: public, max-age=300for HTML;immutablefor hashed assets
Route53 DNS Configuration
Hosted zone: 86dfrom.com (created in Route53)
Records added:
86dfrom.com A (ALIAS)→ CloudFront distribution domain name- ACM validation CNAME records for TLS cert issuance (temporary, removed after validation)
Deployment Pipeline: Scripts & Automation
Static Site Deployment (scripts/deploy.sh)
This bash script handles syncing the site/ directory to S3 and invalidating CloudFront cache:
#!/bin/bash
set -e
# Sync site/ to S3
aws s3 sync ~/Documents/repos/sites/86dfrom.com/site/ \
s3://86dfrom.com/ \
--delete \
--cache-control "max-age=300" \
--region us-east-1
# Invalidate CloudFront cache (all objects)
DISTRIBUTION_ID=""
aws cloudfront create-invalidation \
--distribution-id "$DISTRIBUTION_ID" \
--paths "/*"
echo "✓ Site deployed and cache invalidated"
Why this approach: The --delete flag ensures old files are pruned from S3, preventing stale content from being served. CloudFront invalidation uses /* to clear all cached objects, ensuring new HTML is visible immediately (important for testing checkout flows).
Variant ID Population (scripts/get-printful-variants.js)
This Node.js script fetches product variant IDs from Printful's API and writes them to a JSON file for client-side use:
#!/usr/bin/env node
const https = require("https");
const fs = require("fs");
const PRINTFUL_API_KEY = process.env.PRINTFUL_API_KEY;
const PRODUCT_ID = 1; // Bella+Canvas 3001
function fetchVariants() {
return new Promise((resolve, reject) => {
const options = {
hostname: "api.printful.com",
path: `/v2/catalog/products/${PRODUCT_ID}/variants`,
method: "GET",
headers: {
Authorization: `Bearer ${PRINTFUL_API_KEY}