Building a Printful-Integrated T-Shirt Storefront: Next.js 14, Stripe, and AWS CloudFront CDN
This post covers the complete infrastructure and application build for 86dfrom.com, a print-on-demand t-shirt e-commerce site. We integrated Printful's variant API, Stripe payment processing, Google Apps Script webhooks, and a multi-region CDN strategy across AWS CloudFront and S3.
Architecture Overview
The site uses a three-tier architecture:
- Frontend: Next.js 14 with static HTML generation, deployed to Vercel
- Payment layer: Stripe API integration with webhook ingestion via Google Apps Script
- Print fulfillment: Printful API for product variants and order sync
- CDN / static assets: AWS S3 + CloudFront for fallback and historical deployments
Project Structure
The git repository is organized as follows:
/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/
│ ├── index.html # Main landing page
│ └── success.html # Post-purchase confirmation
├── gas/
│ ├── Code.gs # Google Apps Script webhook handler
│ └── appsscript.json # GAS manifest
├── scripts/
│ └── deploy.sh # S3 + CloudFront deployment script
└── .env.local # Environment variables (Stripe keys, Printful API)
The Next.js application root is at /Users/cb/Desktop/86dfrom (development workspace) and mirrors to ~/Documents/repos/sites/86dfrom.com (version control).
Next.js Build Configuration
The application compiles cleanly with no warnings. All five API routes compile successfully:
/api/variants— Fetch Printful product variants, filtered to Black colorway (IDs 4016–4020)/api/checkout— Create Stripe payment intents/api/webhook— Ingest Stripe webhook events (payment confirmation, refunds)/api/orders— Query order history from order log/api/health— Readiness probe for deployment monitoring
The build was verified with:
cd /Users/cb/Desktop/86dfrom && npm run build
Result: clean build, zero errors, all routes compiled.
Printful API Integration
Printful product variants were fetched via the official REST API. The account is registered under "Hello Dangerous" with store ID 86Store, configured with full scope access across all stores under the dangerouscentaur.com organization.
Why Printful? Printful eliminates inventory management—we push orders directly to them, they handle production, packing, and shipping. The API provides real-time variant metadata (colors, sizes, pricing), which we cache in .env.local to avoid per-request API calls.
Variant selection: The site focuses on the Black colorway exclusively (Bella+Canvas 3001 unisex tee). API calls identified five size variants:
- ID 4016 (XS)
- ID 4017 (S)
- ID 4018 (M)
- ID 4019 (L)
- ID 4020 (XL)
These IDs are hardcoded in the variant API route rather than queried at runtime, reducing latency and API call overhead.
Stripe Payment Processing
Stripe is the payment processor. The integration covers:
- API keys: Publishable key (pk_live or pk_test) is embedded in the frontend; secret key (sk_live or sk_test) is server-side only in
.env.local - Checkout flow:
/api/checkoutcreates a payment intent, returns a client secret, and the frontend uses Stripe.js to render a payment form - Webhook ingestion: Stripe sends events to
/api/webhook; we validate the signature against the webhook secret and log orders on confirmation
Why Stripe over PayPal? Stripe's payment intents API provides superior handling of 3D Secure (SCA) compliance and supports international cards with minimal friction. It also integrates cleanly with Google Apps Script for order logging.
Google Apps Script for Order Logging
Orders are logged to a Google Sheet via Google Apps Script. The GAS project resides at gas/Code.gs and gas/appsscript.json.
Why Google Sheets instead of a database? For a low-volume storefront, a Sheet provides:
- Zero infrastructure (no RDS or DynamoDB costs)
- Built-in audit log (Sheet revision history)
- Easy business review (open a spreadsheet, no SQL)
- Native integration with Google Forms and Apps Script
The webhook handler (deployed via clasp) listens to a webhook endpoint and appends rows on order completion. The .clasp.json file maps the GAS project to the Google Cloud project for clasp deployments.
AWS Infrastructure: S3 and CloudFront
A fallback static deployment pipeline uses AWS S3 and CloudFront. This serves two purposes:
- Historical deployment: If Vercel is temporarily unavailable, the CloudFront distribution serves cached or static HTML from S3
- Static asset acceleration: CSS, fonts, and images are cached at edge locations globally
S3 bucket: Named 86dfrom-site, configured with block-all-public-access enabled and a bucket policy that allows read-only access from the CloudFront distribution via an Origin Access Identity (OAI).
CloudFront distribution: Created with the S3 bucket as the origin. Key settings:
- Domain: Maps to
86dfrom.comvia a Route53 alias record - ACM certificate: Automatically provisioned and validated via DNS (CNAME record in Route53)
- Cache behavior: TTL set to 3600 seconds (1 hour) for HTML, longer for static assets
- Origin policy: Restrict access to the S3 bucket exclusively from CloudFront (no direct S3 URL access)
Deployment script: scripts/deploy.sh syncs the site/ directory to S3 and invalidates the CloudFront cache:
#!/bin/bash
aws s3 sync ./site s3://86dfrom-site --delete
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/*"