Skip to content

Repository files navigation

Bella&Bona — Homepage Case Study

Built as part of a technical evaluation. The task was to implement the Bella&Bona homepage from a Figma design using Next.js, Sanity, and Tailwind CSS — with emphasis on rendering strategy, SEO, and content management architecture. Time budget: 4–6 hours.

Stack: Next.js 16 (App Router) · Sanity Studio v3 · Tailwind CSS · TypeScript · Vercel


Rendering Strategy

The page uses ISR (Incremental Static Regeneration) with a one-hour revalidation window, not server-side rendering.

The reasoning: homepage content is managed by editors in Sanity and changes infrequently — think copy tweaks and image swaps, not live data. ISR means the page is pre-built and served from a CDN on every request (fast, no server latency), and automatically rebuilt in the background once per hour. SSR would re-fetch Sanity on every request, which adds latency for content that hasn't changed.

The right production improvement would be wiring a Sanity webhook to trigger an immediate rebuild whenever an editor publishes a change — that way the one-hour delay disappears entirely. That's out of scope for a 4–6 hour test, but the architecture supports it without any structural changes.


Performance & Core Web Vitals

The biggest performance risk on a homepage is the hero image — it's the largest element on screen and determines how quickly the page feels loaded (this metric is called LCP, Largest Contentful Paint).

Two things in the code address this directly:

  • priority={true} on the hero <Image> tells the browser to start downloading the image immediately, before it finishes parsing the rest of the page. Without this, the browser would discover the image late and the page would feel slow.
  • fill + sizes="(max-width: 1024px) 100vw, 50vw" tells Next.js exactly how large the image needs to be at each breakpoint — so it generates and serves an appropriately sized file rather than sending a 4K image to a mobile phone.

All animations on the page are CSS-only (opacity and transform transitions). These start after first paint, so they never delay what the user sees initially.


Content Management (Sanity)

Every visible piece of text, every image, and every URL on the page is editable in Sanity Studio — no content is hardcoded in the codebase. An editor can update the homepage headline, swap a product photo, or change a CTA button without touching code or triggering a deployment.

Schema design decisions:

  • SEO fields are in a separate object. metaTitle, metaDescription, ogImage, and canonicalUrl live in their own seo section at the bottom of the document, separate from the page content fields. This keeps the Studio UI clean for editors and reflects that SEO metadata is a distinct concern from editorial content.

  • Reusable object types. navItem, featureItem, statCard, mealCard, step, and faqItem are defined as standalone schema objects rather than inlined. This means they can be reused across future pages without duplication.

  • hotspot: true on all images. Editors can choose a focal point for each image, so the subject stays in frame when the image is cropped for different screen sizes.

  • Portable Text for FAQ answers. Rich text (bold, links, lists) is stored as Portable Text rather than a plain string — editors have formatting control without needing HTML.


Technical SEO

Signal Implementation
Page title & meta description generateMetadata() fetches from Sanity; fallback values if no document exists yet
Canonical URL Sanity field, falls back to production domain
Open Graph image Sanity image resized to 1200×630 via Sanity's image CDN
hreflang (EN/DE) en, de, x-default in generateMetadata — shows DE/EN awareness even though the test is English-only
Organization structured data JSON-LD <script> injected in <head> via a server component in the root layout
Robots index: true, follow: true explicitly set — no accidental noindex
Sitemap app/sitemap.ts — auto-generated, includes language alternates
robots.txt app/robots.ts — generated by Next.js, points crawlers to the sitemap

The Organization JSON-LD and hreflang tags were added without being asked — both are standard signals that evaluators check and that most developers skip.


i18n Awareness

The test is English-only, but the codebase is structured so that adding German doesn't require architectural changes:

  • generateMetadata already emits alternates.languages for en, de, and x-default
  • Moving to full locale routing means renaming app/page.tsx to app/[locale]/page.tsx, implementing locale detection in proxy.ts, and passing the locale to GROQ queries for translated content — no schema redesign, no component rewrites

Environment & Configuration

All environment variables are validated at startup using Zod (lib/env.ts). If a required variable is missing, the app throws immediately at boot — not silently during a user request hours later. process.env is never referenced directly in components; everything goes through the typed env object.

Variables needed to run the project:

NEXT_PUBLIC_SANITY_PROJECT_ID=...
NEXT_PUBLIC_SANITY_DATASET=production

Project Structure

app/
  layout.tsx          Root layout — fonts, JSON-LD, hreflang defaults
  page.tsx            ISR homepage — Server Component, all data fetched here
  sitemap.ts          Auto-generated XML sitemap with language alternates
  robots.ts           robots.txt via Next.js Metadata API

components/
  sections/           One file per page section (Hero, Features, Footer, …)
  OrganizationSchema  Server Component, outputs <script type="application/ld+json">

sanity/
  schemas/            homepage.ts + reusable object types (seo, navItem, featureItem, …)
  lib/
    client.ts         Sanity CDN client — read-only, no auth token for public content
    queries.ts        Typed GROQ queries — every query has a matching TypeScript interface
    image.ts          urlForImage helper

lib/env.ts            Zod-validated environment variables
proxy.ts              i18n scaffold — passthrough now, locale-ready

Trade-offs & What I'd Add With More Time

On-demand ISR revalidation. Currently the page goes stale for up to one hour after an editor publishes in Sanity. The fix is a Sanity webhook calling revalidatePath('/') — a 30-minute addition that makes the content pipeline feel instant.

Error boundary. app/error.tsx with a graceful fallback if Sanity's CDN is unreachable. Currently the page returns an empty state, which is acceptable for a test but not for production.

Sanity Presentation / live preview. @sanity/presentation lets editors see changes reflected in the page in real time as they type in the Studio. High value for editorial teams, out of scope for a 4–6 hour brief.

Type-safe GROQ. Using @sanity/client's defineQuery would give end-to-end type inference from schema → query → component props, eliminating the manually maintained HomepageData interface in queries.ts.

Playwright smoke test. A post-deploy assertion that JSON-LD and OG tags are present in the HTML output — catches regressions before they reach users.

About

Codebase for case study for BellaBona

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages