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
previewbranch → deploy topreview.website2026.pages.dev(use this to review changes before merging tomain). - 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):
The pre-push hook runsnpm 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
/→ serves/index.html- Path without a dot and without trailing slash → redirected to
path/(directory redirect) - Path ending with
/→ resolved aspath/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:
- Accepts multiple content types: JSON, Form Data, URL-encoded.
- Normalises custom fields (e.g.,
catalog_size→ formattedmessagestring). - Injects submission context (
pageUri,pageName). - 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
mainbranch. - Workflow:
checkout→npm ci→ deploy viacloudflare/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):
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):
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:
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
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) |