A marketing and product website for ContentMesh, a no-code platform that moves and transforms content data between systems like Google Sheets, Strapi, HubSpot and REST APIs. The site covers the full pre-signup journey — home, product and pricing pages, a searchable connectors catalogue, use-case pages, a blog, and contact/waitlist capture — with lead submissions flowing straight into the company's CRM. I was one of three developers on the build, working through merge requests with preview deployments for every branch.
ContentMesh
A marketing and product site for ContentMesh, a no-code data-connector platform, covering home, pricing, a connectors catalogue, use cases, blog and lead capture. I was one of three developers on the build.

ContentMesh
Live
Introduction

The Challenge
ContentMesh needed a marketing and product site covering the entire pre-signup journey for a no-code data-connector platform: home, product and pricing pages, a searchable connectors catalogue with detail pages, use-case pages, a blog, and contact/waitlist capture feeding straight into the company's CRM. The content set was expected to keep growing, so the site had to make adding a connector, use case or post cheap, and it had to render server-first so content pages stayed light.
The Outcome
A live marketing site covering the whole pre-signup funnel, with leads flowing directly into the CRM and content pages rendered server-first.
Tech Stack
Frontend
- Next.js
- React
- Mantine
- markdown-to-jsx
- Vanilla Extract
- SVGR
- React Server Components
- Mantine UI
Language
- TypeScript
- Zod
- Markdown
Backend
- HubSpot
- JSON-LD
- Strapi
- REST API
DevOps
- Datadog
- Vercel
- Husky
- lint-staged
- GitLab CI
- GitLab
Tooling
- ESLint
- Prettier
- Webpack
- Google Sheets
- Next.js 14 App Router with React Server Components, so most of the marketing pages render on the server and ship minimal client JavaScript
- TypeScript in strict mode across roughly 12,000 lines, with path aliases (
@components,@lib,@ts) keeping imports readable - Mantine 7 as the component library — chosen over Chakra-style alternatives because it isn't Emotion-based and works cleanly with server rendering
- vanilla-extract for styling: type-safe CSS-in-TypeScript compiled to static CSS at build time, wired in via the official Next.js plugin and bridged to Mantine's tokens with
@mantine/vanilla-extract - Zod schemas paired with @mantine/form through
mantine-form-zod-resolver, so the same schema validates on both client and server - HubSpot forms integration behind a Next.js API route for contact, waitlist and newsletter capture
- Datadog RUM and browser logs for real-user monitoring, gated behind a cookie consent component
- Content authored as typed data modules plus Markdown files, rendered with markdown-to-jsx — no CMS dependency to run the site
- GitLab CI → Vercel: branch pushes get preview deployments, merges to
maindeploy to production

Key Implementation Details
Consistent, modular route structure — The strongest thing about this codebase is how easy it is to move around in.
- Every route folder follows the same shape:
page.tsx, a colocated*-data.tscontent module, a*.css.tsstyle file, and asections/directory for the page's composed blocks - Shared UI lives in a deliberately layered
components/tree (content/blocks,content/sections,layout,forms,ui), so a new page is mostly assembly rather than new code - Dynamic routes for connectors, use cases and blog posts all read from the same typed data modules, which keeps the catalogue pages cheap to extend
Server-validated form pipeline — Lead capture is the one place the site does real work.
- Zod schemas live in
lib/forms/schemasand are looked up by form name, so a singlevalidateFormSubmissionhelper covers contact, waitlist and newsletter forms - The HubSpot API route re-validates on the server before submitting, returning a 400 with a structured error array on failure
- Submissions attach HubSpot's
hubspotutktracking cookie and the request IP so form fills are attributed correctly in the CRM, plus explicit legal-consent options
SEO handled at the component level, not bolted on — Structured data is generated where the content lives.
- Page-specific JSON-LD sits alongside each route, while reusable components like breadcrumbs and the FAQ block generate their own schema automatically for every instance
app/sitemap.tsandapp/robots.tsuse the framework's metadata APIs, backed by a shared sitemap data source and a build-time generation script- Full Open Graph and Twitter card metadata is centralised in the root layout, with per-page overrides via typed metadata helpers
Interactive pricing — The pricing page is the most stateful part of the site.
- A React context drives a calculator with billing-period toggles, a usage slider and a multi-currency switcher (flag SVGs and locale-aware formatting through a shared price utility)
- Plan cards, tooltips and dropdown comparisons all read from the same context, so the numbers stay in sync as the user changes inputs
Build and quality tooling — Enforced rather than documented.
- A flat ESLint config covering TS, TSX, YAML and JSON, including type-checked rules, import sorting and unused-import removal
- Prettier via Husky pre-commit and pre-push hooks with lint-staged
- Custom webpack config to turn SVG imports into React components with SVGR, plus
optimizePackageImportsfor Mantine to keep bundles trimmed - Host and trailing-slash redirects handled in
next.config.mjsso canonical URLs stay clean
The friction: vanilla-extract — Worth recording honestly. Type-safe styles are appealing in principle, but the authoring experience was a drag: no imports or PostCSS variables inside .css.ts files meant things like media-query breakpoints had to be hardcoded as raw numbers, and every styling change involved more ceremony than plain CSS or SCSS would have. It works, and the generated CSS is static and fast — but I wouldn't reach for it again on a content-driven site.
What I Contributed
- One of three developers on the build, working through merge requests with a preview deployment for every branch.
- Built the site on Next.js App Router with React Server Components, Mantine and vanilla-extract, in a consistently modular route structure: every route folder pairs a page with a colocated content module, style file and sections directory, so a new page is mostly assembly rather than new code.
- Built the lead-capture pipeline: Zod schemas looked up by form name behind a single validation helper, re-validated server-side in the HubSpot API route, attaching HubSpot's tracking cookie and request IP so form fills attribute correctly in the CRM.
- Generated structured data where the content lives rather than bolting SEO on afterwards — per-route JSON-LD plus reusable breadcrumb and FAQ components that emit their own schema for every instance.
Outcome
-
A genuinely tidy codebase — clean component boundaries, a predictable route structure and enforced tooling — which is what kept a three-person build with frequent merge requests easy to coordinate.
-
No traffic, signup or revenue figures to report: the product did not find commercial traction.
A live, well-organised marketing site that covers the whole pre-signup funnel and feeds leads directly into the CRM, built server-first so the content pages stay light. There aren't traffic or conversion figures to point at, and the product didn't find commercial traction — but the codebase itself is genuinely tidy: clean component boundaries, a route structure you can predict, and consistent tooling that made a three-person build with frequent merge requests painless to coordinate.