```html

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 via scripts/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.com with 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=300 for HTML; immutable for 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}