Back to Clients
Small BusinessAITailwind CSSContentfulNext.jsTypeScript

Ritter Consulting

Shipping a Single-Page Site That's Ready to Grow

Note that Ritter Consulting is currently under construction and will be live soon!

Here's the thing about "just a simple one-pager." Nobody ever really wants that. What they want is a one-pager today, and the confidence that when they ring you up next quarter saying "hey, we should add a case studies section, and maybe a blog, and what about a services deep-dive page," the answer isn't "sure, I'll start over." The answer should be "already done. Pick your colour."

That was the brief for Ritter Consulting. Jon needed a single-page marketing site for his IT consulting practice in the Niagara region. Clean. Professional. On-brand. Fast. And ready to scale without tearing it down and rebuilding from zero. That last part is where the fun started.

The Shape of the Thing

Stack-wise, we went with Vite, React 19, TypeScript, Tailwind v4, Contentful for content, Vercel for hosting, Cloudflare Turnstile for bot protection, and Resend for the contact form emails. A lot of moving parts for one page, I know. But each piece pulls its weight.

The big early call was content modeling. Since the site has to grow, every chunk of copy on the page is a reference to a Contentful entry. Hero copy, service cards, the about section, the contact form options, all of it lives in Contentful. Jon can log in, change a headline, hit publish, and the site redeploys itself within about ninety seconds. No developer in the loop.

To make the scaling part honest, I modelled it as a Page type with a reference array of Section entries. So when the day comes to spin up a /services or /case-studies page, we're not adding plumbing. We're adding content. The plumbing is already there.

The GraphQL Choice

Contentful gives you two ways to read content: their REST SDK or the GraphQL Content API. I went GraphQL, and I'll die on this hill. One query, one round trip, exactly the fields the page needs. No over-fetching, no chaining requests together to resolve references. When the content graph grows (and it will), the query grows with it and the performance profile doesn't budge.

I paired it with graphql-codegen to introspect the schema and spit out fully typed TypeScript, so the entire content layer is type-safe from the query string all the way to the JSX. Refactoring is painless. TypeScript yells if I rename a field in Contentful and forget to update a component.

The Design System

Tailwind v4 is a real departure from v3 if you haven't touched it recently. They moved the whole configuration into CSS, which felt weird for about an afternoon and then felt obvious forever after. You write @theme blocks with CSS custom properties, and Tailwind generates utilities from them. Your design tokens live in the same file as everything else that determines how things look. No more flipping between a JS config and your stylesheet to figure out what bg-primary actually means.

The palette is a burnt orange against a warm off-white, with a secondary charcoal. Clean Material Design 3 inspiration. Ambient shadows instead of hard borders. Glass nav with backdrop blur. Jon wanted "precision architect" energy, which I read as: nothing flashy, everything intentional.

The Contact Form Saga

Here's where we earned our paycheque. The contact form was supposed to be the easy part. Turnstile on the client, a little serverless function on the backend, Resend for delivery. Should have been an afternoon.

First issue: the Turnstile widget wouldn't load on the deployed Vercel preview. Cloudflare Turnstile only trusts hostnames you've explicitly allow-listed, and every Vercel preview deploy has a different URL. Fix: add the stable production domain to the Turnstile config, and remember to use that URL for testing.

Then the real fun. We deploy, we submit the form, and the browser just sits there. Spinning. Thirty seconds. A minute. Nothing. Check the Vercel logs: FUNCTION_INVOCATION_TIMEOUT, 300 seconds, zero outgoing requests. The function was hung before it even called anything.

The culprit? I'd written the handler in Web API style, with a Request coming in and a Response going out. That works great on Vercel's Edge runtime. Except we were on the Node runtime, which calls the handler with Node's classic (req, res) signature. So when my code tried to await req.json(), it was waiting on a method that doesn't exist on a Node IncomingMessage. The promise never resolved. The function just sat there, patient as a golden retriever, for the full five-minute max.

One rewrite to the canonical Vercel Node signature later, and the form was firing email in milliseconds. Lesson learned: check which runtime your handler expects before you get creative with the signature.

The Little Things That Matter

A few touches that aren't sexy but move the needle:

SEO. We built a JSON-LD ProfessionalService schema listing Jon's service area (Niagara, Ontario), what he knows about, and his LinkedIn. Canonical URLs, proper Open Graph tags, a sitemap, a robots.txt. Small-business search is still very much worth optimizing for.

Open Graph image. Generated programmatically with Sharp and an inline SVG, so regenerating gives us a fresh 1200x630 PNG in about half a second. First attempt tried to embed the client's logo, which was a JPEG. JPEGs don't do transparency, so the image came out with an ugly checkered background baked into what should have been the transparent parts. Pivoted to a typography-only editorial design with an "IT" monogram tile. Classier anyway.

Auto-deploys on publish. Vercel's deploy hooks plus Contentful's webhooks mean the site rebuilds automatically whenever Jon publishes content. Edit a headline, hit publish, wait ninety seconds, refresh. Done. No Slack message to a developer. No Jira ticket. Just content, going live.

The Takeaway

A good one-page site isn't a one-page codebase. It's a twenty-page codebase folded neatly so that only one page shows through. The hero and services are components. The contact form is a component. The content that fills them is structured data. When the "hey can we add a page" phone call comes in, we're building on top of architecture that's already there.

Jon's happy. The site's fast. The email lands in his inbox when someone fills out the form. Sometimes the best projects are the ones where you do all the hard thinking up front, and by the end, the hard part looks like it was never hard at all.

That's the gig.