Building a Printful-Integrated T-Shirt Commerce Site on Next.js 14 with AWS CloudFront and Stripe
Over the past session, we built out a complete e-commerce application for 86dfrom.com—a t-shirt dropshipping site powered by Printful, Stripe, and Next.js 14. This post covers the architecture decisions, infrastructure setup, and integration patterns we used to create a production-ready store.
Project Structure & Tech Stack
The application lives in /Users/cb/Documents/repos/sites/86dfrom.com/ and consists of three main layers:
- Frontend: Next.js 14 React application in
/sitewith static HTML fallbacks - Backend: Next.js API routes at
/app/api/handling Printful and Stripe integrations - Infrastructure: AWS S3, CloudFront, Route53, and ACM for DNS, CDN, and SSL
- Deployment Automation: Bash scripts in
/scripts/deploy.shfor CI/CD
The application follows a typical serverless JAMstack pattern: static assets and HTML are served from CloudFront, dynamic API requests hit Vercel's serverless functions, and external integrations (Printful, Stripe) handle fulfillment and payments.
Why Printful for Fulfillment?
Printful's API eliminates inventory management entirely. Rather than holding stock, we:
- Query Printful's catalog to fetch available products and variants
- Retrieve variant IDs (e.g.,
4016,4017,4018,4019,4020for Bella+Canvas 3001 Black in sizes XS–2XL) - Pass variant IDs to Printful at checkout to create orders on-demand
- Printful handles printing, packing, and shipping directly to customers
This is implemented in /app/api/checkout.js, which reads variant IDs from environment variables and constructs a Printful order payload.
Environment & API Key Management
We created .env.local with the following structure:
PRINTFUL_API_KEY=UPQNIqzJkpoV2JPKKrhwYteCKzhipRnLHA2TxLnt
STRIPE_SECRET_KEY=sk_live_... (or sk_test_... for staging)
STRIPE_PUBLISHABLE_KEY=pk_live_...
NEXT_PUBLIC_STRIPE_KEY=pk_live_...
PRINTFUL_VARIANT_IDS=4016,4017,4018,4019,4020
Key points:
PRINTFUL_API_KEYis account-wide with all scopes across all stores (86Store in this case)STRIPE_SECRET_KEYnever leaves the server; it's only referenced in API routesNEXT_PUBLIC_STRIPE_KEYis safe to expose to the browser—it's the publishable key- Variant IDs are pre-fetched from Printful and hardcoded to avoid runtime API calls on every page load
Infrastructure: DNS, SSL, and CDN
We set up a complete AWS infrastructure stack for 86dfrom.com and configured a redirect domain for 86from.com (typo-squatting protection):
Primary Domain (86dfrom.com)
- S3 Bucket:
86dfrom-com-site(region: us-east-1, versioning enabled) - ACM Certificate: Requested for
86dfrom.comand*.86dfrom.comwith DNS validation via Route53 - CloudFront Distribution: Points to S3 origin with HTTP/2, gzip compression, and cache invalidation
- Route53 Hosted Zone: Public zone for
86dfrom.comwith A record aliased to CloudFront distribution
Redirect Domain (86from.com – typo protection)
- S3 Bucket:
86from-com-redirect(region: us-east-1) - CloudFront Function: Custom redirect function (viewer request) that returns HTTP 301 to
86dfrom.com - CloudFront Distribution: Attached to the function for all requests matching
86from.com - Route53 A Record:
86from.comaliased to the redirect CloudFront distribution
The CloudFront Function (deployed to 86from-com-cf-function) uses this logic:
function handler(request) {
return {
statusCode: 301,
statusDescription: 'Moved Permanently',
headers: {
location: {
value: 'https://86dfrom.com'
}
}
};
}
This is far more efficient than maintaining a second site—CloudFront handles the redirect at the edge in milliseconds.
Deployment Pipeline
The deployment script at /scripts/deploy.sh handles:
- Build verification: Runs
npm run buildto ensure Next.js compiles cleanly (all 5 routes:/,/checkout,/success,/api/checkout,/api/webhook) - Vercel deployment:
npx vercel@latest --prodpushes the app to production serverless functions - S3 sync: Syncs static assets to
86dfrom-com-siteS3 bucket - CloudFront invalidation: Invalidates cache with
/*pattern to force edge nodes to pull fresh content
The two-destination approach (Vercel for dynamic routes, S3/CloudFront for static assets) is intentional. It provides:
- Performance: Static HTML, CSS, and fonts are served from CloudFront's global edge network
- Redundancy: API routes fail over gracefully if Vercel is down (the static site still loads)
- Cost efficiency: CloudFront bandwidth is cheaper than Vercel for high-traffic static content
- Control: We own the S3 bucket and can audit all assets; no vendor lock-in
Stripe Integration & Webhook Architecture
Stripe is integrated in two places:
/app/api/checkout.js: Creates a Stripe Checkout Session and redirects to Stripe's hosted page