Back to portfolio
Business Valuation & Financial AdvisoryRedesign

Triumvirate Financial

A Next.js and Sanity rebuild of a boutique valuation firm's GoDaddy site, restructuring four pages into a seven-section architecture with a custom content model, JSON-LD across six schema types, and a scroll-driven editorial design system.

Triumvirate Financial

Case Study

The starting condition. The client ran on GoDaddy Website Builder 8.0 with four pages, seven services crammed into one flat list, and three different company names across the logo, domain, page titles, and contact email. No structured data. Empty alt attributes on every image. Duplicated H1s and literal --- artifacts left in the markup. The homepage headline appeared three times in the DOM as a builder artifact, not by intent.

Why Next.js and Sanity. The site needed to rank for informational valuation queries, which meant server-rendered pages with real metadata per route, not a client-side app. It also needed the client to publish insights and update service copy on their own, since the whole point of the Insights layer is a content cadence I am not in the loop for. Next.js App Router with Sanity as a headless backend covers both: static generation with ISR keeps pages fast and indexable, and Studio gives non-technical editors a browser-based interface with live draft previews.

I considered a flat-file MDX approach, which would have been simpler to build and cheaper to host. I rejected it because the client's team has no technical background, and the editorial workflow is the deliverable, not a side effect.

Content modeling was most of the thinking. The temptation with a seven-page marketing site is one flexible page type with a section array for everything. I split it. Home, About, and Process use a page builder with a typed section array, because those pages are narrative and their structure varies. Services, team members, testimonials, FAQs, and posts are strict document types with defined fields, because they are repeating records that need to be queried, referenced, and rendered as structured data.

That split is what makes the schema work pay off. Because faq is a real document type with a reference to a service, the FAQPage JSON-LD on each service page generates from the same content the visitor reads. Nothing is duplicated in markup, and the schema cannot drift from the page.

The alt text decision. The old site shipped every image with an empty alt attribute. Fixing the existing images is trivial. Preventing recurrence is the actual problem, since the client will be uploading images for years after I stop touching the project. I made alt text a required field on a reusable imageWithAlt object used everywhere an image appears, so the validation fires in Studio at upload time. The editor cannot publish without it. This is the kind of constraint that feels slightly annoying to a content editor and saves an accessibility audit two years later.

The design system. I presented two directions and the client picked across both, taking the layout and interaction model from one and the navy, brass, and cream palette from the other. That combination surfaced a contrast failure immediately: brass on cream measures 2.75:1, well under AA. Rather than shifting the brand color, I inverted the relationship for interactive elements, so button text and hover states run navy on brass instead. The palette survives and the buttons pass.

The motion work is IntersectionObserver with staggered reveal delays, plus a prefers-reduced-motion branch that skips the transition entirely. No animation dependency. For a site this size, a library would have been more bundle weight than the effect is worth.

One interaction I revised after building it: the service listings originally hid their descriptions until hover, revealing them on a row wipe. It looked good and read badly. Content that only exists on hover is content that does not exist on touch devices or for keyboard users. The descriptions now sit visible at rest and the hover enriches rather than reveals.

Where it stands. Phase 1 is live. Phase 2 wires up the content model and Studio configuration. If I were starting over I would build the Sanity schema before the frontend rather than after, since the page builder section types ended up reverse-engineered from components I had already written. It worked, but the dependency ran the wrong direction and I redid a couple of section shapes because of it.

Screenshots