Building a Printful T-Shirt Store on Next.js 14: Full-Stack Setup with AWS CloudFront, Route53, and Google Apps Script
This post documents the complete technical setup for 86dfrom.com, a Printful-integrated t-shirt e-commerce site built on Next.js 14 with Stripe payments, AWS CloudFront CDN delivery, and Google Apps Script backend automation. The project demonstrates a modern serverless architecture pattern combining multiple SaaS platforms with custom infrastructure.
Project Structure and Technology Stack
The project is organized into three distinct deployment surfaces:
- Next.js Frontend (
/site): React 19 with App Router, deployed to Vercel - Google Apps Script Backend (
/gas): Order processing and fulfillment automation - Static Assets: Hosted on AWS S3 with CloudFront CDN, managed via Route53
The development environment is configured in /Users/cb/Documents/repos/sites/86dfrom.com/ with this structure:
86dfrom.com/
├── site/ # Next.js 14 application
│ ├── index.html # Landing page template
│ ├── success.html # Post-purchase confirmation
│ └── [...routes] # API endpoints
├── gas/ # Google Apps Script
│ ├── Code.gs # Fulfillment automation
│ ├── appsscript.json # GAS manifest
│ └── .clasp.json # clasp deployment config
└── scripts/ # Infrastructure automation
└── deploy.sh # S3/CloudFront deployment script
Environment Configuration and Build Verification
The Next.js 14 build compiles cleanly with all five API routes:
/api/create-checkout– Stripe checkout session creation/api/webhook– Stripe event handler/api/get-variants– Printful variant data endpoint/api/[...routes]– Dynamic route catch-all- Static product page routes
The build was verified with npm run build, producing optimized output ready for Vercel deployment. Configuration is managed via environment variables in .env.local:
# Required environment variables
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_[...]
STRIPE_SECRET_KEY=sk_live_[...]
STRIPE_WEBHOOK_SECRET=whsec_[...]
PRINTFUL_API_KEY=[token]
PRINTFUL_STORE_ID=[numeric-id]
Printful API Integration
The Printful integration uses the 86Store account under the "Hello Dangerous" workspace with API scopes covering all stores. Variant data is fetched and cached using a script pattern at scripts/get-printful-variants.js:
// Fetches variant IDs for Bella+Canvas 3001 Black t-shirts
// Required for /api/create-checkout to function
const variants = {
"xs": 4016,
"s": 4017,
"m": 4018,
"l": 4019,
"xl": 4020
}
These variant IDs are hardcoded into the checkout flow, eliminating runtime Printful API calls at payment time and reducing latency.
AWS Infrastructure: S3, CloudFront, and Route53
The static delivery pipeline uses three AWS services working in concert:
S3 Bucket Configuration
An S3 bucket named 86dfrom.com serves as the origin for CloudFront. Bucket policy restricts access exclusively to the CloudFront distribution via origin access identity (OAI), preventing direct S3 HTTP access:
# Bucket: 86dfrom.com
# Policy: CloudFront OAI only
# Versioning: Enabled for rollback capability
# Public access: Blocked entirely
CloudFront Distribution for Primary Domain
The primary distribution (d[...].cloudfront.net) serves 86dfrom.com with:
- Origin: S3 bucket via OAI
- SSL/TLS: AWS Certificate Manager cert for
86dfrom.com - CNAME:
86dfrom.com - Default root object:
index.html - Cache behaviors: 24-hour TTL for HTML, 365-day for versioned assets
- Compression: Gzip for all text content
ACM certificate validation required DNS CNAME records added to Route53:
# Route53 validation records (auto-generated by ACM)
_[random].86dfrom.com CNAME _[random].acm-validations.aws.
CloudFront Redirect Distribution for Variant Domain
A second distribution handles the 86from.com typo variant, implementing client-side redirect logic via CloudFront Functions. The function checks the Host header and redirects to the canonical domain:
// CloudFront Function: redirect-to-canonical
if (request.headers.host.value === '86from.com') {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
'location': { value: 'https://86dfrom.com' + request.uri }
}
}
}
This function is attached to the viewer-request event on the redirect distribution, reducing load on origin infrastructure.
Route53 DNS Configuration
Two A records in the hosted zone for 86dfrom.com direct traffic to CloudFront:
# Primary domain
86dfrom.com A [alias] → d[...].cloudfront.net (primary distribution)
# Redirect variant
86from.com A [alias] → d[...].cloudfront.net (redirect distribution)
Both use Route53 alias records, eliminating additional DNS lookups and providing health checks at the CloudFront level.
Deployment Pipeline
The deployment script at scripts/deploy.sh automates S3 sync and CloudFront cache invalidation:
#!/bin/bash
aws s3 sync ./site s3://86dfrom.com --delete
aws cloudfront create-invalidation \
--distribution-id [ID] \
--paths "/*"
This pattern ensures:
- Atomic updates: Files are synced before cache invalidation
- Full purge:
/*invalidates all CloudFront edge locations globally - Deletion safety:
--deleteremoves orphaned files from S3
Stripe Payment Integration
Checkout flow is implemented in /api/create-checkout:
- Accepts cart items with size selection
- Maps size to Printful variant ID
- Creates Stripe checkout session with line items