```html

Building a Printful-Integrated T-Shirt Store on Next.js 14: Infrastructure and Deployment Strategy

What Was Done

We built a full-stack e-commerce application for 86dfrom.com—a Printful-integrated t-shirt store powered by Next.js 14, Stripe payments, and Google Apps Script webhooks. The project involved scaffolding a clean Next.js application, configuring environment variables across development and production, provisioning AWS S3 and CloudFront infrastructure, setting up DNS across Route53, and deploying to Vercel with integrated Stripe payment processing.

Project Structure and Architecture

The codebase follows a modular Next.js 14 structure:


/Users/cb/Documents/repos/sites/86dfrom.com/
├── site/
│   ├── index.html           (landing page / product showcase)
│   └── success.html         (post-purchase confirmation)
├── gas/
│   ├── Code.gs              (Google Apps Script webhook handler)
│   └── appsscript.json      (GAS manifest and clasp config)
├── scripts/
│   └── deploy.sh            (S3 + CloudFront deployment automation)
├── .env.local               (local development secrets)
└── [Next.js app routes]     (API routes, pages, components)

This hybrid structure serves both static assets (via CloudFront) and dynamic API endpoints (via Vercel). The separation allows us to maintain a fast CDN for marketing assets while keeping compute-intensive operations (Stripe validation, Printful API calls) in a serverless environment.

Environment Configuration Strategy

We implemented a three-layer environment setup:

  • Development (.env.local): Sensitive keys stored locally, git-ignored, includes Printful API token, Stripe test/live keys, and webhook secrets
  • Vercel Production: Environment variables configured via Vercel dashboard, automatically injected at build/runtime
  • Static Assets (S3/CloudFront): No secrets required; signed URLs handled server-side via API routes

This approach isolates secrets from version control while enabling secure deployment across environments. The Stripe webhook endpoint at /api/webhook receives signed payloads, validates them using the webhook secret, and updates order state without exposing keys in transit.

Infrastructure: AWS and DNS Configuration

S3 Bucket for Static Assets

We created a dedicated S3 bucket (86dfrom-site-assets) to host static marketing assets, product images, and downloadables. The bucket is configured with:

  • Block Public Access: Enabled (no direct public access)
  • CloudFront Origin Access Identity (OAI): Bucket policy restricts access to CloudFront distribution only
  • Versioning: Enabled for audit trail and rollback capability
  • Server-Side Encryption: AES-256 default

The bucket policy allows only the CloudFront OAI to read objects, preventing direct S3 URL access and forcing all traffic through the CDN for caching, compression, and DDoS protection.

CloudFront Distribution

Two CloudFront distributions were provisioned:

  1. Primary Distribution (86dfrom.com):
    • Origin: S3 bucket with OAI
    • Default TTL: 3600 seconds (1 hour) for index.html, 31536000 (1 year) for versioned assets
    • Compress: Enabled (Gzip + Brotli)
    • HTTP/HTTPS: HTTPS enforced
    • SSL Certificate: AWS Certificate Manager (ACM) wildcard for *.86dfrom.com
  2. Redirect Distribution (86from.com):
    • CloudFront Functions: Evaluates requests to 86from.com, returns HTTP 301 redirect to https://86dfrom.com
    • Purpose: Capture typo traffic and legacy domain references

The redirect function is lightweight—a single CloudFront Functions script (not Lambda@Edge) that executes at edge locations with minimal latency. This pattern prevents content duplication and centralizes SEO value to the canonical domain.

Route53 DNS and SSL

Route53 hosts the domain with the following record structure:

  • 86dfrom.com (A record): Alias to CloudFront distribution
  • www.86dfrom.com (CNAME): Points to primary CloudFront distribution
  • 86from.com (A record): Alias to redirect CloudFront distribution
  • ACM Validation CNAMEs: DNS validation records for both domains' SSL certificates

We used Route53 alias records (not CNAME) for apex domains—this avoids the CNAME limitation at zone apex and keeps routing logic within AWS.

Vercel Deployment and API Routes

The Next.js application is deployed to Vercel with the following structure:


app/
├── page.tsx             (homepage)
├── api/
│   ├── webhook/         (Stripe webhook handler)
│   ├── checkout/        (Stripe Checkout session creation)
│   └── variants/        (Printful variant ID endpoint)
└── layout.tsx           (root layout)

Key API routes:

  • POST /api/checkout: Accepts product selection, calls Stripe API to create a Checkout session, returns session ID for client redirect
  • POST /api/webhook: Validates incoming Stripe events using webhook secret, processes payment_intent.succeeded events, queues fulfillment to Google Apps Script
  • GET /api/variants: Returns cached Printful variant IDs for Bella+Canvas 3001 Black t-shirts (variants 4016–4020)

We chose Vercel for its native Next.js optimization, built-in environment variable injection, and automatic HTTPS. The serverless function cold-start latency is acceptable for checkout flows where users expect a brief redirect delay.

Printful Integration

Printful API calls are made server-side to avoid exposing the API key in client-side code. The integration fetches product variants on deployment (via a build-time script) and caches them in .env.local:


NEXT_PUBLIC_VARIANTS_4016=Bella+Canvas+Black+S
NEXT_PUBLIC_VARIANTS_4017=Bella+Canvas+Black+M
NEXT_PUBLIC_VARIANTS_4018=Bella+Canvas+Black+L
# etc.

The NEXT_PUBLIC_ prefix means these values are exposed to the browser (safe because they're not credentials—just product metadata). The actual Printful API key (PRINT