Building a Printful-Integrated T-Shirt Commerce Site: Infrastructure, Deployment, and API Integration

This post documents the technical build of 86dfrom.com, a Next.js 14 e-commerce site integrating Printful for on-demand apparel fulfillment, Stripe for payments, and Google Apps Script for backend automation. The project demonstrates modern serverless commerce patterns with a clean separation between static assets, compute, and third-party integrations.

Architecture Overview

The 86dfrom project uses a multi-layer architecture:

  • Frontend: Next.js 14 (App Router) deployed to Vercel with 5 API routes
  • Backend: Google Apps Script (GAS) for order automation and data sync
  • Product Data: Printful API integration for variant management and fulfillment
  • Payments: Stripe for transaction processing and webhook handling
  • DNS/CDN: Route53 + CloudFront (secondary infrastructure for static assets)

Project Structure

The repository lives at /Users/cb/Documents/repos/sites/86dfrom.com/ with three main directories:

86dfrom.com/
├── site/              # Static HTML fallback and success page
│   ├── index.html     # Landing page (single-page, client-rendered)
│   └── success.html   # Order confirmation page
├── gas/               # Google Apps Script backend
│   ├── Code.gs        # Main automation logic
│   └── appsscript.json # GAS manifest and OAuth scopes
└── scripts/
    └── deploy.sh      # S3 + CloudFront deployment script

The Next.js application itself (which handles the actual checkout flow) is deployed to Vercel separately.

Next.js Build Verification

Before deployment, a clean build was executed to verify all 5 API routes compile without errors:

npm run build

The routes implemented are:

  • /api/products — Fetch product variants from Printful
  • /api/orders — Create orders in Printful
  • /api/webhook — Handle Stripe payment webhooks
  • /api/variants — Return cached variant IDs for the Black 3001 Bella+Canvas shirt
  • /api/health — Health check endpoint

All routes compiled cleanly, confirming TypeScript types and imports were correct.

Printful Integration Strategy

Rather than hardcoding product IDs, the project uses a script-based approach to query Printful's API and populate variant IDs dynamically. The script at scripts/get-printful-variants.js was designed to:

  1. Accept a Printful API token as an environment variable
  2. Query the Printful API for the Bella+Canvas 3001 shirt (product ID 1)
  3. Filter variants by color (Black) and print acceptable variant IDs
  4. Output variant IDs in a format ready to paste into .env.local

This approach decouples product data from code and allows the team to add or update variants without redeploying.

Infrastructure: S3 + CloudFront Setup

Beyond the Vercel deployment, static assets were also deployed to AWS CloudFront for geographic redundancy and potential failover:

  • S3 Bucket: 86dfrom-site (us-east-1) — hosts index.html and success.html
  • CloudFront Distribution: Fronts the S3 bucket with a custom domain (cdn.86dfrom.com conceptually)
  • Route53 Hosted Zone: Holds DNS records for 86dfrom.com

The S3 bucket was configured with a bucket policy to allow CloudFront to read objects:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::86dfrom-site/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
        }
      }
    }
  ]
}

An AWS Certificate Manager (ACM) certificate was provisioned for 86dfrom.com with DNS validation via CNAME records added to Route53.

CloudFront Redirect Distribution

A second CloudFront distribution was created for the typo domain 86from.com (missing the "d") to redirect traffic back to the canonical domain. This distribution uses a CloudFront Functions script (published to the viewer-request event):

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

This ensures users landing on the typo domain are immediately redirected to the correct site, improving SEO and user experience.

Google Apps Script Backend

The GAS component (gas/Code.gs) handles order automation and integrations:

// Example function structure
function processOrder(orderId) {
  // Fetch order from Stripe webhook
  // Create fulfillment request in Printful
  // Log to Google Sheets for team visibility
  // Send confirmation email
}

The appsscript.json manifest declares required OAuth scopes for Google Sheets, Gmail, and Printful API access. This approach offloads async tasks from the main request-response cycle and provides a reliable audit trail in Google Sheets.

Environment Configuration

The .env.local file (not checked into git) contains:

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_SECRET_KEY=sk_...
PRINTFUL_API_TOKEN=...
PRINTFUL_STORE_ID=...
VARIANT_ID_BLACK_4016=...
VARIANT_ID_BLACK_4017=...
VARIANT_ID_BLACK_4018=...
VARIANT_ID_BLACK_4019=...
VARIANT_ID_BLACK_4020=...
STRIPE_WEBHOOK_SECRET=whsec_...

The Stripe publishable key is prefixed with NEXT_PUBLIC_ to make it available to client-side code (Stripe Elements initialization). The secret key and webhook secret remain server-only.

Vercel Deployment

The Next.js application was deployed to Vercel production via:

npx vercel@latest