Marketing site for fynix.digital — a studio that helps cybersecurity companies turn their websites into growth engines through UI/UX, technical excellence, AI-ready SEO, and predictable lead generation.
The site is organised around four Acts — the studio's service model — each with its own landing page, deliverables, proof metrics, and linked case studies:
| Act | Slug | Focus |
|---|---|---|
| 01 · UI/UX | ui-ux |
Interfaces that earn trust before a line is read |
| 02 · Development | development |
Technical execution and performance |
| 03 · SEO | seo |
AI-ready organic visibility |
| 04 · Lead Generation | lead-generation |
Predictable, measurable pipeline |
Everything is statically prerendered except the two API routes, so pages ship as HTML and hydrate for motion and interaction.
| Layer | Choice | Version |
|---|---|---|
| Framework | Next.js (App Router, Turbopack) | 16.2.10 |
| UI runtime | React | 19.2.4 |
| Language | TypeScript | 5.9.3 |
| Styling | Tailwind CSS v4 (CSS-first @theme) |
4.3.2 |
| Animation | Motion | 12.42.2 |
| Smooth scroll | Lenis | 1.3.25 |
| WebGL | OGL | 1.0.11 |
| Map rendering | dotted-map | 3.1.0 |
| Error monitoring | @sentry/nextjs |
10.68.0 |
| Transactional email | Brevo API | — |
| Uptime | Better Stack | — |
| Hosting | Vercel | — |
| Linting | ESLint (eslint-config-next) |
9.39.5 |
Requirements: Node.js >=20.9.0 (enforced by Next 16) and npm.
git clone https://github.com/digibranders/fynix-digital-v2.git
cd fynix-digital-v2
npm install
cp .env.example .env # then fill in the values below
npm run devOpen http://localhost:3000.
| Variable | Scope | Required | Purpose |
|---|---|---|---|
BREVO_API_KEY |
Server | Yes, for the contact form | Sends the admin notification and user auto-reply |
SENTRY_AUTH_TOKEN |
Build | Production only | Uploads source maps so production stack traces are readable |
NEXT_PUBLIC_SITE_URL |
Client | Optional | Overrides the canonical URL used in metadata |
SENTRY_DSN / NEXT_PUBLIC_SENTRY_DSN |
Server / Client | Optional | Override the DSN; the project DSN is hardcoded as a fallback |
VERCEL_ENV, NEXT_PUBLIC_VERCEL_ENV and VERCEL_PROJECT_PRODUCTION_URL are injected by Vercel and need no local configuration.
Never commit .env — it is gitignored, and SENTRY_AUTH_TOKEN and BREVO_API_KEY are both secrets.
| Command | Does |
|---|---|
npm run dev |
Dev server with Turbopack |
npm run build |
Production build (uploads source maps when SENTRY_AUTH_TOKEN is set) |
npm start |
Serve the production build |
npm run lint |
ESLint |
npx tsc --noEmit |
Typecheck |
app/ App Router pages, all Server Components
api/contact/ Contact form → Brevo (admin + auto-reply)
api/lead-diagnostic/ Act-specific diagnostic capture
case-studies/[slug]/ Per-client case studies
services/[slug]/ Per-Act service pages
global-error.tsx Root-layout error boundary → Sentry
robots.ts Generated robots.txt
sitemap.ts Generated sitemap.xml
components/ Client Components ("use client")
lib/
content.ts Single source of truth: acts, case studies, siteConfig
email/ Brevo client, templates, HTML previews
emphasize.tsx Renders **bold** spans inside content strings
scripts/ One-off asset generation
instrumentation*.ts Sentry init per runtime
sentry.*.config.ts Sentry init per runtime
| Path | Type |
|---|---|
/ |
Static |
/about, /process, /faqs, /contact |
Static |
/privacy, /terms |
Static |
/services, /services/[slug] |
SSG (4 acts) |
/case-studies, /case-studies/[slug] |
SSG (5 case studies) |
/robots.txt, /sitemap.xml |
Generated |
/api/contact |
Dynamic (Node runtime) |
/api/lead-diagnostic |
Dynamic |
/monitoring |
Sentry tunnel (rewrite, bypasses ad blockers) |
- Page-level components are Server Components. Anything interactive lives in
components/behind"use client". - State is local. React hooks only — no global store.
- Content is data. Copy, metrics, deliverables and case studies live in
lib/content.ts, not in JSX.**double asterisks**in content strings are rendered as emphasis bylib/emphasize.tsx. - Design tokens are defined in
app/globals.cssvia Tailwind v4's@theme. Use the semantic names (--color-primary,--color-text-muted, …) rather than raw values. - See
CLAUDE.mdandDESIGN.mdfor the fuller architectural and visual direction.
Note:
AGENTS.mdflags that this is Next.js 16 — APIs and conventions differ from older versions. Checknode_modules/next/dist/docs/before relying on remembered patterns.
Sentry is wired across all three runtimes (browser, Node, Edge) via instrumentation.ts, with onRequestError capturing server component, route handler and server action failures, and app/global-error.tsx catching root-layout crashes.
| Signal | Status |
|---|---|
| Errors | On, unsampled |
| Tracing | On, 10% in production (100% in dev) |
| Logs | On, console capture at warn/error |
| Session Replay | Off |
| Profiling | Off |
Replay and profiling are deliberately disabled. Sentry bills quotas per organization, not per project, so the free plan's 50 replays/month are shared across every project in the org and are worth more on the app projects than on a marketing site. Continuous profiling is not included on the free plan at all.
Privacy: request bodies are excluded from every runtime (dataCollection: { httpBodies: [] }) because the contact and lead-diagnostic routes receive names, emails, phone numbers and free-text messages. Console capture is scoped to warn/error for the same reason — app/api/lead-diagnostic/route.ts logs submitter details at log level, which must not reach Sentry.
/api/contact validates the submission (required name, email, phone and at least one service; 2000-character field cap) then sends two transactional emails through Brevo: an admin notification and a user auto-reply.
All 15 outbound emails share one design system.
| File | Holds |
|---|---|
lib/email/design.ts |
Palette, type scale, document shell, and the primitives every template composes. The only file that touches raw colour or opens a <table>. |
lib/email/templates.ts |
Contact form and Technical SEO Audit, customer reply plus internal notification. |
lib/email/pavelTemplates.ts |
The workshop lifecycle: priority list, paid confirmation, reminders, certificate, recording, missed-you. |
To see them, render the gallery and open it:
npm run email:previewThat writes .email-preview/ (gitignored). Serve it with the email-preview entry in .claude/launch.json, or open .email-preview/index.html directly. Every template is rendered from source with sample data, alongside its plain-text alternative.
The masthead uses public/email/logo.png, exported from components/Logo.tsx. Re-run npx tsx scripts/generate-email-logo.tsx after any change to the logo component. It has to be a raster image: no mainstream mail client renders an SVG <img>.
lib/email/templates.test.ts asserts the invariants that hold across every template (escaping, one masthead and one footer, no 8-digit hex, no SVG images, subject and body agreeing on what they promise). Add a template, and it is covered automatically once it is listed there.
Buyers must receive our emails and only our emails. Zoom will otherwise send its own, and its registration confirmation carries the join link, which defeats the rule that the link is withheld until the one-hour reminder.
Create the Zoom webinar from the Semantic SEO Masterclass (Fynix emails only) template. Zoom copies every setting from a template except panelists, alternative host, and time, so a webinar created this way starts silenced. Created from scratch it does not: a new webinar defaults to sending a registration confirmation, and there is no account-level setting that changes that default. It is per webinar, every time.
If a webinar is ever created without the template, open Manage → Email Settings and confirm all five rows read "No ...":
| Row | Required |
|---|---|
| Panelist invite | No Invitation Email to Panelists |
| Confirmation email | No Confirmation Email Sent to Registrants |
| Reminder email | No reminder email to Attendees and Panelists |
| Attendee email | No follow-up email to Attendees |
| Absentee email | No follow-up email to Absentees |
Zoom shows a standing banner urging you to turn the confirmation email back on ("We recommend turning it on or sending your own confirmation email"). We do send our own. Ignore it.
To verify without the UI, read the live settings back:
npx tsx scripts/zoom-webinar-emails.ts <webinarId>Registration must also stay on Manually Approve. registerAttendee pushes paid buyers in with auto_approve, which Zoom only honours on a manually-approved webinar; switching to automatic approval opens the public registration page as a second, unpaid door into the session.
Designed, not built. No code for this exists yet.
A control layer for the company's own outgoings: a registry of every recurring
subscription, a ledger of the invoices each one is expected to produce, and
multi-channel collection (vendor API, billing mailbox, manual upload) that files
the documents in Cloudflare R2 and makes a missing one impossible to overlook.
It becomes a third section of the admin console at /admin/finance, using the
existing gateway, session and console primitives.
Note the naming trap: the existing invoices table means tax invoices Fynix
issues to workshop buyers. Everything in that system is prefixed finance_
and the inbound artifact is called a document, never an invoice.
| Document | Holds |
|---|---|
docs/finance/architecture.md |
Architecture review, gap analysis, target design, and the critique of the prior spec |
docs/finance/data-model.md |
Every table, column, index, constraint and state machine |
docs/finance/connector-architecture.md |
Capability model, connector interface, per-vendor research with sources |
docs/finance/failure-recovery.md |
Failure matrix, retry and backoff policy, the pre-implementation self-critique |
docs/finance/security-model.md |
Secrets, least privilege, audit, threat model, vendor-credential policy |
docs/finance/operations-runbook.md |
Vendor checklist, month-end close, what to do when each thing breaks |
docs/finance/implementation-plan.md |
Files, routes, workers, env vars, migrations, tests, rollout, rollback |
Hosted on Vercel. vercel.json restricts automatic deployments to main:
Work happens on development and reaches production by merging to main. SENTRY_AUTH_TOKEN must be set in the Vercel project environment for production source maps — Vercel sets CI=1, so the Sentry upload report appears in the build log.
/api/lead-diagnosticcurrently logs submissions and returns success. It does not yet dispatch email, hit a CRM webhook, or persist anything.components/Header.tsxhas a standing ESLint error (setStateinside an effect) and two unused-variable warnings.- The cron trigger is not in version control.
/api/pavel/cron/remindersis invoked on a schedule configured outside this repository:vercel.jsondeclares nocronsand there is no systemd unit or crontab here. A schedule nobody can see is a schedule nobody can verify, and nothing currently detects the timer having stopped.docs/finance/operations-runbook.md §6has a slot to record it, and the finance work adds a heartbeat so a stalled scheduler becomes visible.
{ "git": { "deploymentEnabled": { "*": false, "main": true } } }