The marketing website for Jibriva, a Nigerian One Health consultancy. Built with Next.js (App Router), TypeScript and Tailwind CSS, from the specification in JIBRIVA-HANDOFF/.
- 9 marketing routes (Home, About, Services, Expertise, Our Work, Insights, Contact, Privacy, Terms) plus a custom 404
- Every page is a React Server Component; the contact form is the site's only Client Component
- All copy lives in typed
content/*.tsfiles, not hardcoded in components — see Content management below - Design tokens (colors, spacing, type scale) are frozen per
JIBRIVA-HANDOFF/DESIGN_SYSTEM.mdand ported into Tailwind's@themeconfig inapp/globals.css
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict) |
| Styling | Tailwind CSS v4 (CSS-first @theme config, no tailwind.config.ts) |
| Fonts | next/font/google — Inter, weights 400–800 |
| Forms | React Hook Form + Zod (contact form validation) |
| Images | next/image throughout |
| Hosting | Vercel (zero-config Next.js preset) |
app/ Routes — one folder per URL segment, each with a page.tsx
layout.tsx Root <html>, font loading, global metadata
sitemap.ts /sitemap.xml, generated from config/site.ts
robots.ts /robots.txt
not-found.tsx Custom 404 (no Navbar/Footer)
components/
layout/ Navbar, Footer
navigation/ NavLink
sections/ Page sections (Hero, OurApproach, FounderSection, FinalCta, ...)
cards/ Repeated card patterns (ProjectCard, InsightCard, ...)
ui/ Primitives (Button, Badge, Card, Section, Pill, ...)
forms/ ContactForm (the one "use client" component)
content/ One typed module per content area — see below
config/site.ts Nav links, footer link groups, contact info, social URLs
hooks/useContactForm.ts React Hook Form + Zod wiring for the contact form
lib/
metadata.ts buildMetadata() — shared per-route SEO metadata builder
schema.ts JSON-LD builders (Organization, ProfessionalService, Person)
types/content.ts Shared TypeScript interfaces for all content shapes
utils/cn.ts Tiny classname-join helper
public/
logos/, icons/ Brand assets
team/ Team photos (e.g. founder portrait)
og-image.png Shared Open Graph image
Requires Node.js 18.18+ and npm.
npm installnpm run devOpen http://localhost:3000. Every page except /contact is a Server Component, so most edits hot-reload instantly.
npm run build # production build — must complete with zero TypeScript/ESLint errors
npm run start # serve the production build locally
npm run lint # ESLint onlyVercel is the target host:
- Import the GitHub repo into Vercel — the Next.js preset is auto-detected, no custom build command needed.
- Set
NEXT_PUBLIC_SITE_URL(see below) to the production domain before the first deploy, so canonical/OG URLs and the sitemap don't leak a preview URL. - Attach the custom domain (
jibriva.com) once DNS is ready.
The site has zero required environment variables — it builds and every page (including the contact form's success state) renders correctly with nothing set. These are optional, for follow-up work:
NEXT_PUBLIC_SITE_URL=https://jibriva.com # canonical/OG domain; defaults to https://jibriva.com
# Contact form has no submission backend yet — pick exactly ONE when wiring one:
FORMSPREE_FORM_ID=
RESEND_API_KEY=
EMAILJS_SERVICE_ID=
EMAILJS_TEMPLATE_ID=
EMAILJS_PUBLIC_KEY=
# Optional, only if analytics is added later:
NEXT_PUBLIC_GA_MEASUREMENT_ID=
NEXT_PUBLIC_PLAUSIBLE_DOMAIN=Set these in Vercel under Project → Settings → Environment Variables, scoped per environment.
Every piece of visible copy and every image reference lives in content/, config/site.ts, or public/ — never hardcoded inside a component. Editing the site almost never requires touching a .tsx file.
| To change... | Edit... |
|---|---|
| Nav links, footer link groups, contact email/WhatsApp, social URLs | config/site.ts |
| Home page copy (hero, all section headers/cards) | content/home.ts |
| About page copy (mission/vision/values, pull quote, approach intro) | content/about.ts |
| Founder bio, expertise, journey, philosophy | content/founder.ts |
| Services categories and sub-items | content/services.ts |
| Sectors and Platforms & Tools (Expertise page) | content/expertise.ts |
| Our Approach timeline steps (both Home and About variants) | content/timeline.ts |
| "Why Organisations Choose Jibriva" list | content/why-jibriva.ts |
| Final CTA band copy | content/cta.ts |
| Contact form labels/placeholders/success copy | content/contact.ts |
| Privacy Policy / Terms of Service body | content/legal.ts |
| 404 page copy | content/not-found.ts |
Add an entry to the projects array in content/our-work.ts:
{
slug: "your-project-slug",
tag: "Category Tag",
name: "Project Name",
status: "Live" | "In Development" | "Coming Soon",
overview: "One paragraph describing the platform.",
problem: "The problem this project addresses.",
tech: ["Tech", "Stack", "Items"],
href: "https://...", // omit entirely if not Live — the "Explore Platform →"
// link only renders when href is set
}To also feature it in the homepage teaser, add its slug to ourWorkTeaser.slugs in content/home.ts (max 3 shown).
Do not fabricate outcomes or claims for non-Live projects — this is a deliberate brand rule (see BRAND_GUIDE.md), not a technical constraint.
Add an entry to the insights array in content/insights.ts:
{
slug: "your-article-slug",
topic: "Topic Tag",
title: "Article Title",
teaser: "One-sentence teaser.",
}To feature it on the homepage teaser, add its slug to insightsTeaser.slugs in content/home.ts (max 3 shown). The InsightArticle type in types/content.ts also has optional coverImage, author, date, readingTime, categories, tags and body fields, scaffolded for a future /insights/[slug] MDX-driven detail page — not built yet, since no articles have real body content today.
Add a category to content/services.ts (ServiceCategory[]), or a sub-item to an existing category's items array:
{ name: "Sub-item name", value: "One-sentence, outcome-first description of what this does for the client." }Note: the sub-item copy in
content/services.tsand the full Privacy/Terms body text incontent/legal.tswere drafted during this rebuild — the original source files (services.dc.html,privacy.dc.html,terms.dc.html) that CONTENT_GUIDE.md defers to were not included in the handoff package. Every drafted string is flagged with a// DRAFTED — review before launchcomment at the top of both files. Rungrep -rn "DRAFTED" content/to find everything that still needs the client's real source copy before public launch.
All images live under public/ and are referenced by path from content/ or directly in components via next/image:
- Logo / favicon / OG image: replace the files in
public/logos/,public/icons/,public/og-image.pngdirectly (keep the same filenames, or update the references incomponents/layout/Navbar.tsx,components/layout/Footer.tsx,app/not-found.tsx, andlib/schema.ts). - Founder portrait: replace
public/team/abi-precious-jibrin.pngdirectly (same filename = zero code changes). It renders at a fixed 4:5 aspect ratio incomponents/sections/FounderSection.tsx. - One Health Hub product screenshot:
components/sections/FeaturedProject.tsxcurrently renders the documented placeholder treatment (striped panel + logo + caption) at a reserved 16:10 aspect ratio. Replacing it with a real screenshot means swapping that placeholder<div>for anext/imageinside the same aspect-ratio container — the only image slot on the site that still needs a small code change rather than a file swap, because no real screenshot exists yet.
- Drafted copy — see the Content management note above. Grep for
DRAFTEDincontent/. - Contact form has no submission backend — it validates and shows a "Message received." success state, but nothing is sent anywhere yet. Wire one of Formspree/Resend/EmailJS (see Environment variables above).
- Featured Project screenshot — still the placeholder panel; swap in a real One Health Hub screenshot when available.
- Our Approach's 6-column timeline (
components/sections/OurApproach.tsx, home variant) is a fixed 6-column grid that does not reflow below its content width, by design perDESIGN_SYSTEM.md§4.5 — flagged there as a known limitation pending design sign-off, not a bug.