Skip to content

Website & Marketing — Internal Code Documentation

The WEARFITS web presence consists of three separate repositories that together cover the main website, campaign landing pages, and CRM/demo integrations. Each is an independent deployment, but they share the wearfits.com domain and HubSpot as the CRM backend.

Repo URL Platform
website2026 wearfits.com Next.js on Cloudflare Pages
wearfits-marketing wearfits.com/lp2/* Cloudflare Worker + static assets
hubspot-api hubspot-api.wearfits.workers.dev Cloudflare Worker (API + demo pages)

1. Main Website (website2026)

The main website is a Next.js application deployed to Cloudflare Pages.

Environments

Environment URL
Production wearfits.com
Staging website2026.pages.dev
Preview preview.website2026.pages.dev
Blog blog.wearfits.com

Deployment

  • Pushes to main → automatic deploy to staging (website2026.pages.dev).
  • Pushes to preview branch → deploy to preview.website2026.pages.dev (use this to review changes before merging to main).
  • Production (wearfits.com) is promoted manually from staging.

Contact Page URL Parameters

The /contact page supports query parameters to pre-configure the form:

Parameter Value Effect
?v=schedule_a_meeting Opens the calendar booking tab
?s=sales sales | partnership | media | support Pre-selects the email subject in the contact form

Examples:

https://wearfits.com/contact?s=sales
https://wearfits.com/contact?s=partnership
https://wearfits.com/contact?v=schedule_a_meeting

Development Setup

npm install
npm run dev      # http://localhost:3000
npm run build    # Production build
npm run start    # Local production server
npm test         # ESLint + build check (also runs in pre-push hook)

Environment configuration uses separate files per context: - .env.local — local development - .env.development — development environment - .env.preview — preview environment - .env.production — production environment

Git Hooks (required):

git config core.hooksPath .githooks
The pre-push hook runs npm test (lint + build) before each push.


2. Marketing Landing Pages (wearfits-marketing)

The marketing landing pages repository serves static HTML/CSS/JS campaign pages through a Cloudflare Worker acting as an edge router, asset server, and lead relay.

Architecture

graph TD
    User([User Client])
    CFW[Cloudflare Worker - src/worker.js]
    PublicAssets[(Static Assets - public/)]
    HubSpot[HubSpot CRM API]
    TryOn[Try-on Platform - dev.wearfits.com]

    User -- "GET wearfits.com/lp2/:slug" --> CFW
    CFW -- "strip /lp2 prefix, fetch index.html" --> PublicAssets
    PublicAssets -- "HTML/CSS/JS" --> User

    User -- "POST /lp2/api/create-ticket" --> CFW
    CFW -- "normalise fields, proxy" --> HubSpot

    User -- "GET wearfits.com/try/*" --> CFW
    CFW -- "302 Redirect" --> TryOn

URL Pattern

All landing pages are served under wearfits.com/lp2/{slug}, where each slug corresponds to a folder in public/:

public/
├── shared/           # Global CSS, JS, fonts, logos (used by all landings)
├── expo-riva-schuh/  # Example: trade show landing
└── <slug>/           # One folder per landing page campaign

The worker strips the /lp2 prefix before resolving assets, so public/{slug}/index.html is what actually gets served.

Repository Structure

src/worker.js     # Core Cloudflare Worker logic
public/           # Static landing page bundles
  shared/         # Assets shared across all landings
  <slug>/         # One independent landing per folder
tests/            # Node.js end-to-end tests for routing and status codes
wrangler.toml     # Worker config, routing patterns, ASSETS binding

Worker Behaviour

Prefix Stripping

Every inbound request to /lp2/* has the prefix stripped before the path is passed to the ASSETS binding. This keeps the public/ directory structure clean (no /lp2/ folder needed).

Asset Resolution Rules

  1. / → serves /index.html
  2. Path without a dot and without trailing slash → redirected to path/ (directory redirect)
  3. Path ending with / → resolved as path/index.html

Header Injection (withHeaders)

All asset responses receive injected headers:

Header Value
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
Cache-Control Immutable for versioned assets; no-cache for HTML
X-Robots-Tag noindex (applied to all /lp2 paths via isNoIndexPath)

HubSpot Lead Relay (handleCreateTicket)

POST /lp2/api/create-ticket proxies lead form submissions to HubSpot:

  1. Accepts multiple content types: JSON, Form Data, URL-encoded.
  2. Normalises custom fields (e.g., catalog_size → formatted message string).
  3. Injects submission context (pageUri, pageName).
  4. Proxies the payload to api.hsforms.com.

This avoids exposing HubSpot credentials to the browser and allows field normalisation at the edge.

Try Redirects

GET /try/*302 redirect to the try-on platform (dev.wearfits.com). Used for short marketing links.

Legacy Redirects

ROOT_REDIRECTS in worker.js handles legacy shortcut URLs.

CI/CD

GitHub Actions handles automated deployment:

  • Trigger: Push to main branch.
  • Workflow: checkoutnpm ci → deploy via cloudflare/wrangler-action.
  • Required secret: CLOUDFLARE_API_TOKEN.

Development

npm install
npm run dev      # Local worker via Wrangler (http://localhost:8787)
npm run deploy   # Manual production deploy
npm test         # End-to-end routing tests (Node.js assert + fetch)

Adding a landing page: 1. Create public/<new-slug>/index.html (and any assets). 2. Shared resources are available from ../shared/. 3. Add a test in tests/<new-slug>.test.mjs.

Adding a test:

const response = await fetch('http://localhost:8787/lp2/your-landing');
assert.strictEqual(response.status, 200);


3. HubSpot Integration Worker (hubspot-api)

The hubspot-api repository is a Cloudflare Worker that serves demo pages and acts as a RESTful backend proxy for HubSpot CRM operations.

Architecture

sequenceDiagram
    participant Browser
    participant Worker as Cloudflare Worker
    participant Turnstile as CF Turnstile API
    participant HubSpot as HubSpot CRM API v3

    Browser->>Worker: POST /api/create-ticket (data + Turnstile token)
    Worker->>Turnstile: Validate token (siteverify)
    Turnstile-->>Worker: success: true / false

    alt Bot detected
        Worker-->>Browser: 403 Forbidden
    else Verified
        Worker->>HubSpot: Search contact by email
        alt Contact exists
            Worker->>HubSpot: PATCH contact (update)
        else New contact
            Worker->>HubSpot: POST contact (create)
        end
        Worker->>HubSpot: POST ticket (create in PIPELINE_SALES)
        Worker->>HubSpot: PUT association (link ticket ↔ contact)
        HubSpot-->>Worker: 200 OK
        Worker-->>Browser: { message, ticketId }
    end

Demo Pages

The worker serves demo pages as static assets:

Route Description
/demo General product demo
/demo-photo-to-ar Photo-to-AR demo
/demo-tryon AR try-on demo (GTM event tracking)

API Endpoints

POST /api/create-ticket

Creates a HubSpot ticket in the Sales pipeline, linked to a contact. Used by contact forms on demo pages.

Authentication: Cloudflare Turnstile verification (bot protection).

Request:

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "message": "I would like to book a demo.",
  "turnstileToken": "XXX-XXX-XXX"
}

Workflow: 1. Verify turnstileToken with Cloudflare Turnstile. 2. Search HubSpot for an existing contact by email. 3. Create contact if not found; update if found. 4. Create a ticket in PIPELINE_SALES. 5. Associate the ticket with the contact.

Success response (200):

{ "message": "Ticket created successfully", "ticketId": "123456789" }

POST /api/request-enhancement

Creates a support ticket for 3D model enhancement requests. Designed for programmatic use by internal applications.

Authentication: Origin header validation (no Turnstile; only authorised domains are accepted).

Request:

{
  "email": "tech@partner.com",
  "modelId": "93410e704294e818dc15612dcb1abec7",
  "enhancementType": "re-topology",
  "notes": "Please improve the texture resolution."
}

Workflow: 1. Validate that Origin header is in ALLOWED_ORIGINS. 2. Create or update HubSpot contact. 3. Create ticket in PIPELINE_SUPPORT. 4. Associate ticket with contact.

Success response (200):

{ "message": "Enhancement request submitted", "ticketId": "987654321" }

Error Handling

Status Meaning
200 Request succeeded
400 Missing required fields or invalid data
403 Invalid origin or failed Turnstile verification
405 Non-POST request to an API endpoint
500 Internal worker error or HubSpot API error

Error response format:

{ "error": "Error message", "details": "Optional HubSpot API error or stack trace" }

Analytics & Tracking (GA4 Custom Events)

All events are pushed via dataLayer.push({ event: '...' }) and captured by a single GTM tag.

GTM Trigger regex: ^(dev_|saas_|tryon_|shoe_|product_|web_|demo_|2d3d_|contact_|support_|pricing_).*

dev.wearfits.com Events

Event Page Description
dev_signin_click /account/login Magic link sign-in click
dev_uploaded_file /upload New file uploaded
dev_editor_saved /editor Scene saved
dev_obj_footwear_editor /object Footwear tryon editor opened
dev_obj_bag_editor /object Accessory editor opened
dev_obj_delete /object Object deleted

dash.wearfits.com Events

Event Page Description
saas_signin_click /auth Sign-in form submitted
saas_onboarding_ai /onboarding AI tile selected
saas_onboarding_ar /onboarding AR tile selected
saas_newkey API key created
saas_upgrade_click Free user clicks upgrade

tryon.wearfits.com Events

Event Description
tryon_selfie_added Face photo captured/uploaded
tryon_silhouette_added Body photo or measurements submitted
tryon_activated "Create Look" clicked
tryon_generated Fitting result received
shoe_uploaded Shoe image(s) added
shoe_generate "Generate AR Try-On" clicked

wearfits.com Events

Event Pages Description
web_contactus_click All with ContactCTA "Contact Us" click
web_book_click All with ScheduleMeetingCTA "Book a Demo" click
web_signup_click All with GoToDemoCTA "Get Started" click
web_pricing_click_f_* /pricing Footwear plan button (starter/pro/scale/contact/free)
web_pricing_click_c_* /pricing Clothing plan button (free/basic/pro)
demo_qr_* /demo QR code click (footwear/handbag/backpack/clothing)

HubSpot Demo Events

Event Source Page Description
tryon_product_activation /demo-tryon User switches product in the AR viewer
pricing_signup_click /pricing-stripe User clicks a "Start Trial" button
tryon_claim_trial /demo-tryon User clicks the primary CTA in the AR view

Configuration

wrangler.toml environment variables:

Variable Description
ALLOWED_ORIGINS Comma-separated list of domains authorised to call the API
PIPELINE_SALES HubSpot pipeline ID for sales tickets
PIPELINE_SUPPORT HubSpot pipeline ID for support tickets

Secrets (set via wrangler secret put):

Secret Description
HUBSPOT_ACCESS_TOKEN Private app token for HubSpot CRM API access
TURNSTILE_SECRET Cloudflare Turnstile secret key for bot verification

Deployment

npx wrangler deploy

CORS is enforced — only origins in ALLOWED_ORIGINS can call the API endpoints. All requests and responses use application/json.

Base URLs:

Environment URL
Production hubspot-api.wearfits.workers.dev (or custom domain)
Local localhost:8787 (via Wrangler)