Building a Print-on-Demand T-Shirt Store: Next.js 14, Printful API, and AWS CloudFront Infrastructure
Over the past development session, we built out a complete print-on-demand t-shirt commerce platform at 86dfrom.com. This post documents the full stack: Next.js 14 API routes, Printful inventory integration, Stripe payment processing, and a multi-region AWS infrastructure spanning S3, CloudFront, Route53, and ACM.
Architecture Overview
The platform is structured as a modern full-stack Next.js application with serverless compute on Vercel, static asset delivery via CloudFront, and DNS management through Route53. The core workflow is:
- Frontend: Next.js pages and API routes deployed to Vercel
- Print fulfillment: Printful API integration for real-time inventory and order submission
- Payment processing: Stripe for checkout and webhook-based order confirmation
- Static hosting: S3 bucket for site assets with CloudFront distribution for global delivery
- DNS: Route53 for domain management and traffic routing
Next.js Build and Route Structure
The application compiles cleanly with zero build errors. The five core routes are:
/– Homepage with product catalog and variant selector/api/variants– Endpoint to fetch available Printful product variants (sizes, colors, prices)/api/checkout– Creates a Stripe checkout session; validates variant IDs against Printful inventory/api/webhook– Stripe webhook endpoint to handlecheckout.session.completedandpayment_intent.succeededevents/success– Post-purchase confirmation page
Each route is defined as a file in the /pages or /pages/api directory. The API routes use Next.js request/response handlers to call external services (Printful, Stripe) server-side, keeping API keys secure and avoiding client-side token exposure.
Printful API Integration Strategy
Printful serves as the inventory and fulfillment backend. Rather than hardcoding variant IDs, we implemented a dynamic discovery pattern:
- Script-based variant fetching: A Node.js script at
scripts/get-printful-variants.jsauthenticates to Printful using an API key and queries the product catalog for a specific product (in this case, Bella+Canvas 3001 Black t-shirt). - Variant ID extraction: The script parses Printful's response and extracts the numeric variant IDs (e.g.,
4016, 4017, 4018, 4019, 4020for sizes XS through 3XL). - Environment-based configuration: These IDs are stored in
.env.localas comma-separated values in a variable likePRINTFUL_VARIANT_IDS, ensuring they're versioned separately from code and easily rotatable.
The /api/variants endpoint reads these IDs from the environment, then calls Printful's GET /products/{id} endpoint to fetch real-time pricing, availability, and metadata. This decouples our application from Printful's product ID scheme—if we want to add more variants or switch products, we update the environment variable and re-run the discovery script.
Stripe Payment Webhook Architecture
The /api/webhook endpoint is critical for order fulfillment. The flow is:
- User completes checkout on Stripe-hosted checkout page; Stripe sends a signed webhook event to our endpoint.
- We verify the webhook signature using the Stripe CLI secret (obtained after Vercel deployment).
- On
checkout.session.completed, we extract the session metadata (variant ID, size, quantity) and submit an order to Printful viaPOST /orders. - Printful confirms the order, assigns a tracking number, and begins production.
The webhook secret is environment-specific (test vs. live), so we store it in .env.local under STRIPE_WEBHOOK_SECRET. Stripe's CLI will output this value after we configure the endpoint URL in the Stripe dashboard.
AWS Infrastructure: S3, CloudFront, and Route53
To ensure global availability and fast asset delivery, we built a redundant infrastructure:
- S3 bucket:
86dfrom-sitestores static assets (HTML, CSS, fonts, images). The bucket policy is restricted to CloudFront only, preventing direct public access. - CloudFront distribution: Created with origin pointing to the S3 bucket. The distribution uses edge locations worldwide for sub-100ms latency on asset delivery.
- ACM certificate: We requested a wildcard certificate for
*.86dfrom.comand a certificate for the base domain. Validation was done via DNS CNAME records added to Route53. - Route53 hosted zone: Created for
86dfrom.comwith NS records pointing to Route53 nameservers. All DNS traffic for the domain is now routable through AWS. - Redirect distribution: A separate CloudFront distribution handles the typo domain
86from.com, redirecting requests to the canonical domain via a Lambda@Edge function.
The redirect function is published as an edge Lambda, invoked on every request viewer-facing event. This ensures SEO-friendly redirects (HTTP 301) without exposing the redirect logic in the origin bucket.
Environment Configuration and Secrets Management
The .env.local file contains:
PRINTFUL_API_KEY– Authenticated token for Printful API callsPRINTFUL_VARIANT_IDS– Comma-separated variant IDs for the target productSTRIPE_SECRET_KEY– Stripe API secret for backend payments (test or live)STRIPE_PUBLISHABLE_KEY– Public key for frontend redirect to Stripe checkoutSTRIPE_WEBHOOK_SECRET– Secret to verify incoming webhook signatures
After Vercel deployment, these values are also added to the Vercel project settings via the dashboard or CLI, ensuring encrypted storage and automatic injection into the runtime environment.
Deployment Pipeline
The deployment script at scripts/deploy.sh orchestrates the full stack:
- Build Next.js application locally to verify compilation.
- Deploy to Vercel using
npx vercel@latest --prod, which automatically triggers a build and assigns a production URL. - Sync static assets to the S3 bucket using AWS CLI with the
--deleteflag to remove stale files. - Invalidate the CloudFront distribution cache with a wildcard invalidation path (
/*), ensuring users receive fresh assets immediately.
This pipeline ensures that API route updates on Vercel and static asset updates on S3/CloudFront