Building a Printful-Integrated T-Shirt Commerce Site: Infrastructure, Deployment, and API Integration
This post documents the technical build of 86dfrom.com, a Next.js 14 e-commerce site integrating Printful for on-demand apparel fulfillment, Stripe for payments, and Google Apps Script for backend automation. The project demonstrates modern serverless commerce patterns with a clean separation between static assets, compute, and third-party integrations.
Architecture Overview
The 86dfrom project uses a multi-layer architecture:
- Frontend: Next.js 14 (App Router) deployed to Vercel with 5 API routes
- Backend: Google Apps Script (GAS) for order automation and data sync
- Product Data: Printful API integration for variant management and fulfillment
- Payments: Stripe for transaction processing and webhook handling
- DNS/CDN: Route53 + CloudFront (secondary infrastructure for static assets)
Project Structure
The repository lives at /Users/cb/Documents/repos/sites/86dfrom.com/ with three main directories:
86dfrom.com/
├── site/ # Static HTML fallback and success page
│ ├── index.html # Landing page (single-page, client-rendered)
│ └── success.html # Order confirmation page
├── gas/ # Google Apps Script backend
│ ├── Code.gs # Main automation logic
│ └── appsscript.json # GAS manifest and OAuth scopes
└── scripts/
└── deploy.sh # S3 + CloudFront deployment script
The Next.js application itself (which handles the actual checkout flow) is deployed to Vercel separately.
Next.js Build Verification
Before deployment, a clean build was executed to verify all 5 API routes compile without errors:
npm run build
The routes implemented are:
/api/products— Fetch product variants from Printful/api/orders— Create orders in Printful/api/webhook— Handle Stripe payment webhooks/api/variants— Return cached variant IDs for the Black 3001 Bella+Canvas shirt/api/health— Health check endpoint
All routes compiled cleanly, confirming TypeScript types and imports were correct.
Printful Integration Strategy
Rather than hardcoding product IDs, the project uses a script-based approach to query Printful's API and populate variant IDs dynamically. The script at scripts/get-printful-variants.js was designed to:
- Accept a Printful API token as an environment variable
- Query the Printful API for the Bella+Canvas 3001 shirt (product ID 1)
- Filter variants by color (Black) and print acceptable variant IDs
- Output variant IDs in a format ready to paste into
.env.local
This approach decouples product data from code and allows the team to add or update variants without redeploying.
Infrastructure: S3 + CloudFront Setup
Beyond the Vercel deployment, static assets were also deployed to AWS CloudFront for geographic redundancy and potential failover:
- S3 Bucket:
86dfrom-site(us-east-1) — hostsindex.htmlandsuccess.html - CloudFront Distribution: Fronts the S3 bucket with a custom domain (
cdn.86dfrom.comconceptually) - Route53 Hosted Zone: Holds DNS records for
86dfrom.com
The S3 bucket was configured with a bucket policy to allow CloudFront to read objects:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::86dfrom-site/*",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
An AWS Certificate Manager (ACM) certificate was provisioned for 86dfrom.com with DNS validation via CNAME records added to Route53.
CloudFront Redirect Distribution
A second CloudFront distribution was created for the typo domain 86from.com (missing the "d") to redirect traffic back to the canonical domain. This distribution uses a CloudFront Functions script (published to the viewer-request event):
function handler(event) {
var request = event.request;
var headers = request.headers;
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': {
value: 'https://86dfrom.com' + request.uri
}
}
};
}
This ensures users landing on the typo domain are immediately redirected to the correct site, improving SEO and user experience.
Google Apps Script Backend
The GAS component (gas/Code.gs) handles order automation and integrations:
// Example function structure
function processOrder(orderId) {
// Fetch order from Stripe webhook
// Create fulfillment request in Printful
// Log to Google Sheets for team visibility
// Send confirmation email
}
The appsscript.json manifest declares required OAuth scopes for Google Sheets, Gmail, and Printful API access. This approach offloads async tasks from the main request-response cycle and provides a reliable audit trail in Google Sheets.
Environment Configuration
The .env.local file (not checked into git) contains:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_SECRET_KEY=sk_...
PRINTFUL_API_TOKEN=...
PRINTFUL_STORE_ID=...
VARIANT_ID_BLACK_4016=...
VARIANT_ID_BLACK_4017=...
VARIANT_ID_BLACK_4018=...
VARIANT_ID_BLACK_4019=...
VARIANT_ID_BLACK_4020=...
STRIPE_WEBHOOK_SECRET=whsec_...
The Stripe publishable key is prefixed with NEXT_PUBLIC_ to make it available to client-side code (Stripe Elements initialization). The secret key and webhook secret remain server-only.
Vercel Deployment
The Next.js application was deployed to Vercel production via:
npx vercel@latest