Building a Print-on-Demand T-Shirt Store: Full-Stack Deployment of 86dfrom.com

This post documents the complete infrastructure and application build for 86dfrom.com, a Next.js 14 print-on-demand storefront integrated with Printful and Stripe. We'll cover the architecture decisions, deployment pipeline, and exact resource configuration that got this project from local development to production.

Project Overview

The 86dfrom project is a modern e-commerce site serving as a t-shirt marketplace. It uses:

  • Frontend: Next.js 14 with 5 API routes for product variants, checkout, and webhook handling
  • Print Provider: Printful API for inventory and fulfillment
  • Payments: Stripe for transaction processing
  • Hosting: Vercel for application, S3 + CloudFront for static assets
  • DNS: Route53 for domain management

Application Build: Clean Compilation

The Next.js project was scaffolded with all necessary routes and compiled cleanly with no errors. The build output confirmed all 5 API routes compile successfully:

$ npm run build
# Output: Successfully compiled 5 routes
# Zero build warnings

This is critical because it means the entire deployment pipeline can proceed without fixing application code. The project structure follows standard Next.js conventions:

/app
  /api
    /[route-name]
      /route.ts
  /page.tsx
/public
/scripts
  /deploy.sh
  /get-printful-variants.js
/.env.local
/next.config.js

Infrastructure Architecture

Why this stack? Vercel handles dynamic content and API routes with automatic scaling. S3 + CloudFront distributes static assets globally with minimal latency. Route53 provides DNS management in the same AWS ecosystem, reducing operational complexity.

S3 Bucket Configuration

A new S3 bucket was created specifically for 86dfrom.com static assets:

Bucket name: 86dfrom-site-production
Region: us-east-1
Versioning: Enabled
Public Access: Blocked (CloudFront handles distribution)

The bucket policy was configured to allow CloudFront's Origin Access Identity (OAI) to read objects, preventing direct public S3 access while maintaining secure asset delivery:

{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity [OAI-ID]"
  },
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::86dfrom-site-production/*"
}

CloudFront Distribution

A CloudFront distribution was created to serve both the S3 bucket and Vercel origin:

Distribution ID: [dynamically assigned]
Domain Name: d[random].cloudfront.net
Default TTL: 86400 (24 hours)
Minimum TTL: 1
Cache Behaviors:
  - API routes → Vercel origin (no cache)
  - Static assets → S3 origin (aggressive cache)
  - HTML → Vercel origin (short TTL for freshness)

The distribution uses two origins:

  • S3 Origin: 86dfrom-site-production.s3.us-east-1.amazonaws.com for /public/* assets
  • Vercel Origin: 86dfrom.vercel.app for dynamic content and API routes

Route53 DNS Configuration

The hosted zone for 86dfrom.com was created in Route53. Records added:

  • A Record (Root): 86dfrom.com → CloudFront distribution (alias)
  • CNAME Record (www): www.86dfrom.com → CloudFront distribution (alias)
  • ACM Validation CNAME: Added automatically for certificate validation

Using alias records instead of CNAME at the root is critical—Route53 natively supports alias targets, avoiding the DNS misconfiguration that CNAME at root causes.

SSL/TLS Certificate

An ACM certificate was requested for both the apex domain and wildcard:

Subject: 86dfrom.com
Subject Alt Names: *.86dfrom.com
Validation Method: DNS
Status: Validated via Route53 CNAME records

Deployment Pipeline

Vercel Deployment

The application is deployed to Vercel using:

$ npx vercel@latest --prod

This command:

  • Detects the Next.js project automatically
  • Builds the application in Vercel's environment (identical to local build)
  • Deploys to production immediately (no staging step)
  • Provides the deployment URL (e.g., 86dfrom.vercel.app) for CloudFront origin

Environment variables are configured in Vercel's dashboard (Settings → Environment Variables) rather than committed to Git—this keeps secrets out of the repository while allowing the production build to access them.

Static Asset Deployment

The /site directory contains pre-built HTML files (index.html and success.html) deployed directly to S3:

$ aws s3 sync ./site s3://86dfrom-site-production/ --delete

After S3 sync completes, the CloudFront cache is invalidated to ensure fresh content:

$ aws cloudfront create-invalidation \
  --distribution-id [DISTRIBUTION_ID] \
  --paths "/*"

This full-path invalidation is overkill for development but safe for production—it ensures old versions never serve to end users. A production optimization would invalidate only changed files.

Printful Integration

The Printful API key was configured in environment variables with full store scope. A Node.js script populates variant IDs from Printful's API:

// scripts/get-printful-variants.js
const printful = require('@printful/sdk');
client = new printful.PrintfulClient(process.env.PRINTFUL_API_KEY);

// Fetches all variants for Bella+Canvas 3001 in Black
client.get('products/[PRODUCT_ID]/variants').then(variants => {
  // Extract and log variant IDs
});

These IDs are hardcoded into the application because Printful variant IDs don't change. This is more efficient than API calls on every request—the variant catalog is static data.

Stripe Payment Integration

Stripe keys (public and secret) are stored in environment variables. The webhook endpoint is configured at:

/api/webhook

This route is exempted from CSRF protection in Next.js and listens for Stripe events. After Vercel deployment, the webhook URL will be registered