AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Projects
  3. Hvitarnes

Hvitarnes

December 1, 20254 min read
View Project
Next JsTypescriptTailwind CSSPrismic
1 / 4

Overview

Hvitarnes is the marketing website for Hvitarnes, an Icelandic country villa. It presents the property through an editorial, image-led design — a full-bleed parallax hero, galleries, video, and staggered image-and-text sections — built to sell the location to prospective guests.

The entire site is content-driven by Prismic. Two custom types (page and layout) and a 22-slice library let the marketing team compose pages without touching code, while SEO metadata and structured data ship with every page.

There is no backend or database. The one interactive form (the Contact slice) is a client-side component whose submission currently only flips to a success state locally — the marketing content and animations are the core of the deliverable.

Tech Stack & Rationale

Layer

Technology

Why

Framework

Next.js 16 (App Router), React 19

Server-first rendering with a dynamic [[...slug]] route

Language

TypeScript

Strict typing for Prismic documents and slices

Styling

Tailwind CSS 4

Utility-first styling with a project theme

CMS

Prismic + Slice Machine

All page and layout content, versioned locally

Animation

Framer Motion 12

Reveal and parallax animations across slices

Carousel

Embla Carousel React

Hero and gallery carousels

Lightbox

yet-another-react-lightbox

Full-size image preview in galleries

Structured data

schema-dts

JSON-LD typing for SEO

Code hygiene

knip

Dead-code and unused-export detection

Architecture

Every page is a Prismic page document rendered through the catch-all route, composed from slices and wrapped in a layout document that supplies the navbar and footer. Slices are server-rendered by default, with only interactive pieces (Contact form, galleries, hero parallax) marked "use client".

The flow runs: Browser request → [[...slug]]/page.tsx (tag-cached Prismic fetch) → SliceZone (dynamic slice map) → client slices (hero · gallery · contact) → static output (ISR + revalidate). The page's linked layout document supplies the navbar, children, and footer from the same Prismic API.

  1. Routing — the (prismic)/[[...slug]] catch-all renders any UID as a page document; the root page.tsx resolves the homepage the same way.
  2. Fetching — src/shared/config/prismicio.ts creates a Prismic client with a production tag cache (prismic, redirects) and 5-second revalidation in development.
  3. Composition — the page's slice zone is mapped through the generated slices/index.ts component registry, and the linked layout document supplies the navbar/children/footer chrome.
  4. Interactivity — only the Contact form, Hero (parallax), Gallery (lightbox), and carousel slices hydrate on the client; everything else stays a server component.
  5. SEO — src/shared/utils/generateMetaData.ts builds metadata from the Prismic "SEO & Metadata" tab, and pages emit JSON-LD structured data via schema-dts.

Folder Structure

hvitarnes/
├── app/
│ ├── (routes)/(prismic)/[[...slug]]/ # catch-all page renderer
│ ├── (routes)/not-found.tsx
│ ├── api/ # preview · revalidate · exit-preview
│ └── layout.tsx · globals.css · slice-simulator/
├── src/
│ ├── components/features/Hero/ # HeroBackground, HeroTitle, HeroMainImage, HeroBottomContent
│ ├── components/ui/icons/ # ArrowIcon, CheckmarkIcon, PinIcon
│ ├── shared/config/prismicio.ts
│ ├── shared/constants/ # parallax, spacing
│ ├── shared/hooks/ # useParallaxScroll, useImageHeight, useResize, useScrollPosition
│ ├── shared/types/ · shared/utils/ # prismic.types, generateMetaData
├── slices/ # 22 slices (Hero, Gallery, Contact, StaggeredImagesWithText, …)
├── customtypes/ # page, layout
├── public/ # static assets
├── prismicio-types.d.ts · slicemachine.config.json · next.config.ts
└── tailwind/ · tsconfig.json · package.json

Content Model

Prismic is the sole content source, with two custom types:

Custom type

Kind

Purpose

page

Repeatable

Marketing pages — uid, a layout link, a route text field, a 19-choice slice zone, and SEO metadata

layout

Repeatable

Site chrome — a slice zone of navbar, children, and footer slices linked from each page

The page slice zone exposes the full marketing library: hero, banner, gallery, video, contact, card_grid, quote, paragraph, text_with_image, image_with_icon_list, image_data_overlay, intro_three_column_grid, split_intro_grid, split_list, staggered_grid, staggered_images_with_text, text_column, document_header, and floating_badge_section.

Impact & Results

  • In production as the marketing site for Hvitarnes country villa (Iceland), driven entirely by editors through Prismic.

My Contributions

Built the Hvitarnes site from scratch as the lead frontend developer:

  • I initialized the project and set up Prismic + Slice Machine with the page/layout custom types and slice zone.
  • I built the slice library (22 slices), including the Hero with parallax scroll, the Gallery with lightbox preview, and the StaggeredImagesWithText and SplitIntroGrid layouts.
  • I added Framer Motion reveal animations across the slice components and layouts.
  • I built the mobile versions of the hero, navbar, footer, and staggered slices to match the desktop design.
  • I standardized container spacing with a reusable padding/margin field and spacing constants.
  • I upgraded Next.js to 16.0.10 to fix CVE-2025-55183 and cleaned up ESLint errors.

References

[1] Hvitarnes, "Hvitarnes," hvitarnes.is. [Online]. Available: https://hvitarnes.is

[2] Vercel, "Next.js Documentation," nextjs.org. [Online]. Available: https://nextjs.org/docs

[3] Prismic, "Prismic Documentation," prismic.io. [Online]. Available: https://prismic.io/docs

[4] Framer Motion, "Motion Documentation," motion.dev. [Online]. Available: https://motion.dev/docs

[5] Tailwind CSS, "Tailwind CSS Documentation," tailwindcss.com. [Online]. Available: https://tailwindcss.com/docs

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!