← All work

ennkar.com Platform

The public web platform, staff portals, and SEO program behind Ennkar.

Next.jsReactTypeScriptTailwind CSSPayload CMSNeonSupabaseRow-Level SecurityResendZodPlaywrightGA4VercelTurborepopnpm
Role
Lead engineer
Period
May 2026 – Present
Status
Live
Stack
Next.js 16, Payload CMS on Neon, Supabase.
ennkar.com homepage hero, showing the reverse-mortgage estimate call to action

The problem

A reverse-mortgage lender needs a public site that educates homeowners, captures qualified leads, ranks for a genuinely competitive set of search terms, and hands staff a way to work those leads — without ever putting borrower PII where a content editor could accidentally expose it.

Constraints

  • Content editors need CMS access without ever touching operational borrower or lead data.
  • Lead attribution has to be trustworthy enough to report on, without overclaiming multi-touch or identity-resolution capability the system doesn't have.
  • SEO output — ~150 location pages, comparison pages, guides — has to stay correct as content scales, not just at launch.
  • Ad-platform conversion tracking needs to work under consent gating, without leaking PII into the conversion payload.

What I built

A Next.js 16 App Router site inside a pnpm/Turborepo monorepo, deliberately split across two databases: Payload CMS on Neon Postgres holds content only, and Supabase holds operational borrower and lead data. The stated rule is simple — no PII in the CMS — and the architecture enforces it rather than relying on discipline.

  • A fail-closed lead-attribution pipeline: derivePersistedQuizLeadAttribution() computes attribution server-side in a Next.js proxy route, forwards it to a submit-quiz-lead Edge Function behind a shared secret, persists an allowlisted set of 10 metadata keys, and records 'unknown' for anything it can't verify rather than trusting client input.
  • An OpenAI Ads pixel and Conversions API integration with a shared eventId for deduplication, gated on GPC and marketing consent, with no PII in the payload.
  • A technical-SEO program: roughly 150 city location pages across 16 states, 146 of 150 with unique metadata, 6 competitor-comparison landing pages, and 30 learning-center guides, backed by a CI-integrated 'SEO autopilot' with a persistence gate, a circuit breaker, and auto-merge.
  • Staff CRM surfaces with soft-delete, RLS, and audit triggers, capped Server Actions (a 200-ID limit), an open-redirect guard on return-to URLs, and a two-step confirm with a 6-second auto-disarm on destructive actions.

Architecture

Content and operational data are architecturally separated rather than merely access-controlled within one database: Payload on Neon for pages, learning-center content, and comparison landings; Supabase for leads, borrower interactions, and role approvals, with RLS on all ~26 public tables across 35 migrations. Roughly 11 Edge Function areas handle pricing, lead intake, Twilio Verify, FRED rate sweeps, role approval, and quiz intake — the same pricing Edge Function the mobile app calls.

Ennkar Platform architectureEnnkar Connect (Expo mobile) and ennkar.com (Next.js web) both call one server-authoritative pricing Edge Function over Supabase Postgres with Row-Level Security. Content lives in a separate Payload-on-Neon database with no PII. Scheduled Postgres jobs sweep FRED for interest rates; Twilio Verify and Resend handle SMS and email at the edges.rate sweepSMS OTPemailEnnkar ConnectExpo · React Nativeennkar.comNext.js 16Pricing Edge FunctionDeno · server-authoritativeSupabase PostgresRLS on every public tablePayload CMSContent on Neon — no PIIpg_cron pipelinesRate sweeps, remindersFREDInterest ratesTwilio VerifySMS OTPResendEmail
Content (Payload/Neon) and operational data (Supabase) stay in separate databases by design.

Decisions and tradeoffs

Decision

Keep content (Payload on Neon) and operational borrower data (Supabase) in two separate databases.

Because

It makes 'no PII in the CMS' an architectural fact rather than a policy that a future editor or migration could quietly violate.

Trade-off

Any feature that genuinely needs both content and operational data — like a personalized landing page — has to join across two systems instead of one query.

Decision

Record 'unknown' attribution rather than guessing when a lead-source signal can't be verified server-side.

Because

A wrong attribution is worse than an honest gap, since it corrupts every report built on top of it.

Trade-off

This is deliberately single-touch, first-party attribution — it does not do multi-touch, session stitching, or identity resolution, and the reporting has to be read with that scope in mind.

Note

The staff bulk-actions surface described above was built on the feature/staff-leads-bulk-actions branch as of this writing and had not yet merged to main — described here as built, not shipped.

Outcome

Status
Live at ennkar.com
RLS-protected operational tables
~26
Location pages
~150 across 16 states
Unique location metadata
146 / 150
Comparison landing pages
6
Learning-center guides
30

As of 2026-08-24

This is a publicly browsable platform with an unusually direct overlap with the requirements this portfolio itself was built against — technical SEO, structured data, and a CI-integrated regression program for exactly that.

Want the full picture, or open to talking about your own project?