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.comfor/public/*assets - Vercel Origin:
86dfrom.vercel.appfor 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