Building a Printful-Integrated T-Shirt Commerce Site: Next.js 14, Google Apps Script, and AWS CloudFront Infrastructure

This post documents the technical implementation of 86dfrom.com, a full-stack t-shirt merchandise site built with Next.js 14, integrated with Printful's print-on-demand API, and deployed across Vercel and AWS CloudFront. We'll cover the architecture decisions, infrastructure setup, and deployment patterns used.

Project Architecture Overview

The project is structured as a monorepo with three primary components:

  • Next.js 14 application (/site) — React-based storefront with API routes for product variants and Stripe webhook handling
  • Google Apps Script (/gas) — Serverless backend for fulfillment notifications and order management
  • Deployment infrastructure — AWS S3, CloudFront, Route53, and ACM for static assets and DNS management

The tech stack reflects a pragmatic choice: Next.js handles dynamic commerce logic and Stripe integration, while Printful's API provides fulfillment abstraction. Google Apps Script manages backend notifications without requiring a traditional server.

Next.js 14 Build Configuration

The application was scaffolded with Next.js 14 and compiles cleanly with five primary routes:

  • / — Product display and variant selection
  • /api/variants — Printful variant ID endpoint (populates Black variant SKUs 4016–4020)
  • /api/checkout — Stripe Checkout session creation
  • /api/webhook — Stripe webhook receiver for payment events
  • /success — Post-purchase confirmation page

The build passes without errors when running npm run build, confirming all TypeScript types resolve and route handlers are valid. This clean compile state is critical before deployment to Vercel's production environment.

Printful API Integration Pattern

Printful provides variant IDs for each product size and color. The integration script (scripts/get-printful-variants.js) queries the Printful API to fetch these IDs dynamically:

// Pseudocode pattern
const printfulVariants = await fetch('https://api.printful.com/stores/{storeId}/products', {
  headers: { 'Authorization': `Bearer ${PRINTFUL_API_KEY}` }
});

// Extract variant IDs (e.g., 4016 for Black XS, 4017 for Black S, etc.)
const variantMap = processVariants(printfulVariants);

These variant IDs are stored in environment variables and referenced at checkout to ensure accurate SKU mapping. For 86Store on the dangerouscentaur.com Printful account, we're using the standard Bella+Canvas 3001 Black unisex t-shirt with five size variants.

Why this approach: Hardcoding variant IDs is brittle. Fetching them programmatically allows the store to adapt if variants change without code redeploy. The script runs before deployment, populating .env.local with authoritative IDs.

AWS Infrastructure: S3, CloudFront, and Route53

The site's static assets and redirect logic run on AWS, not just Vercel, for several reasons:

  • Edge caching — CloudFront distributes static HTML/CSS globally with sub-100ms latency
  • Domain redirect pattern86from.com (typo variant) redirects to 86dfrom.com via CloudFront Functions, eliminating the need for a separate server
  • Cost optimization — S3 + CloudFront is cheaper than running all traffic through Vercel for static content

S3 Bucket Configuration

Two S3 buckets were created:

  • 86dfrom.com-site — Primary site bucket
  • 86from.com-redirect — Typo domain redirect

Both buckets have public read access configured via bucket policy. The policy grants s3:GetObject to CloudFront's origin access identity (OAI), ensuring only the CloudFront distribution can read objects, not the internet at large.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::cloudfront:user/CloudFront OAI {OAI_ID}" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::86dfrom.com-site/*"
  }]
}

CloudFront Distributions

Primary distribution (86dfrom.com):

  • Origin: S3 bucket 86dfrom.com-site
  • Viewer protocol policy: Redirect HTTP to HTTPS
  • Default TTL: 3600 seconds (1 hour) for HTML, longer for versioned assets
  • Compress: Enabled (gzip/brotli)
  • Certificate: AWS ACM certificate for 86dfrom.com (validated via Route53 CNAME)

Redirect distribution (86from.com):

  • Uses a CloudFront Function to intercept all requests and return HTTP 301 redirects to https://86dfrom.com{URI}
  • Certificate: ACM certificate for 86from.com (same validation method)

The redirect function is deployed via CloudFront's Function editor:

function handler(event) {
  return {
    statusCode: 301,
    statusDescription: 'Moved Permanently',
    headers: { 'location': { value: 'https://86dfrom.com' + event.request.uri } }
  };
}

Why Functions over Lambda@Edge: CloudFront Functions execute at edge locations with sub-millisecond latency, no cold starts, and no execution charges for redirects. Lambda@Edge has higher latency and cost.

Route53 DNS Records

Route53 hosted zone for 86dfrom.com contains:

  • Alias record: 86dfrom.com → {primary_cf_distribution_domain}
  • Alias record: www.86dfrom.com → {primary_cf_distribution_domain}
  • Alias record: 86from.com → {redirect_cf_distribution_domain}
  • CNAME record: ACM certificate validation records (auto-generated, typically _*.86dfrom.com CNAME _*.acm-validations.aws.`)

ACM certificates were requested for both domains and validated via Route53 DNS, eliminating manual email verification delays.

Vercel Deployment and Environment Variables

The Next.js application deploys to Vercel via:

npx vercel@