A fast, SEO-first, destination-led marketing website for Sea Saba, a professional scuba diving operation on the island of Saba in the Dutch Caribbean.
See the testing guide for the audit, coverage, fixtures, CI and required merge check. With Node 24, run npm ci, npx playwright install chromium webkit, then npm run test:ci. For fast feedback, use npm test or npm run test:watch.
This project is a migration from Wix to a custom Next.js stack.
The website is intentionally designed as a premium, destination-first marketing site, not a generic dive shop catalog.
The homepage is a positioning + routing page, while inner pages carry the SEO-rich informational depth.
The site should communicate:
- Why dive Saba
- Why choose Sea Saba
- What experiences are available
- How to take the next step
- Destination-first — sell Saba as a unique diving destination
- Experience-led — emphasize boat diving, signature sites, and trip planning
- Trust-first — calm, professional, safety-forward
- SEO-first — semantic, indexable, stable content architecture
- Conversion-ready — clear routing and strong Checkfront booking paths
This is not intended to be a retail-heavy or generic dive shop template.
- Framework: Next.js 16 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS 4 + shadcn/ui
- Content: TSX pages for marketing / informational / dive site content
- Dynamic Data: Firestore (dives, boats, sites, species, guides)
- Booking: Checkfront (deep links, embedded widgets)
- Analytics: Vercel Analytics, Google Analytics 4 (optional), Google Tag Manager (optional)
- PDF Export: jsPDF (premium card-style dive log export)
- Deployment: Vercel
npm install
cp .env.example .env.local
# fill in .env.local with your Firebase values
npm run devOpen http://localhost:3000 to view the site.
There are exactly two layouts in this project:
- Static hero image at top
- One homepage video background section (3rd major section)
- Minimal copy
- Destination positioning + routing
- Strong but restrained CTA hierarchy
- Used for all non-home pages
- No video backgrounds
- Text-first, SEO-focused
- Breadcrumbs encouraged
- Semantic, content-rich, stable
Do not introduce additional layouts unless explicitly requested.
The homepage must follow this high-level structure:
- Hero (Static Image)
- Supporting Static Section
- Single Video Section + CTA
- Static Routing Sections
- Final CTA
The homepage should answer:
- Why dive Saba?
- Why Sea Saba?
- What can I choose?
- Where do I go next?
- The homepage is not a content dump
- The homepage is not the primary SEO text page for every topic
- Detailed content belongs on inner pages
- Motion is minimal and only used on the homepage
- Mobile video must degrade to a poster image
app/
├── layout.tsx # Root layout (header, footer, fonts, SEO, JSON-LD)
├── page.tsx # Homepage (destination-first layout)
├── not-found.tsx # Custom 404 page
├── robots.ts # robots.txt
├── sitemap.ts # sitemap.xml
├── book/page.tsx # Booking page (Checkfront widget + fallback)
├── (content)/ # Standard content layout group
│ ├── layout.tsx # Content layout (breadcrumbs, prose, SEO-first)
│ ├── about/
│ ├── contact/
│ ├── courses/
│ ├── dive-log/
│ ├── dive-sites/
│ ├── diving/
│ ├── partners/
│ ├── plan-your-trip/
│ ├── privacy/
│ └── terms/
components:
├── ui/ # shadcn/ui components
├── header.tsx # Sticky header
├── footer.tsx # Site footer
├── footer-wrapper.tsx # Footer wrapper
├── hero.tsx # Homepage hero
├── breadcrumbs.tsx # Breadcrumb navigation
├── booking-cta.tsx # Reusable booking call-to-action
├── booking-widget.tsx # Checkfront embedded widget
├── dive-log-client.tsx # Interactive dive log UI
├── find-sea-saba.tsx # Map / location component
└── structured-data.tsx # JSON-LD LocalBusiness structured data
lib/
├── metadata.ts # SEO metadata helpers
├── constants.ts # Site-wide constants (URLs, nav items)
├── firebase.ts # Firebase client SDK setup
├── firestore/
│ ├── dive-log.ts # Firestore dive log fetching and normalization
├── dive-log-export.ts # Premium PDF export for selected dives
└── ...
public/
├── images/ # Site images (hero, OG, posters, content, logo)
└── video/ # Homepage video assets
- Project scaffolding (Next.js, Tailwind, TypeScript)
- shadcn/ui setup and design system
- Root layout with Header and Footer
- Homepage layout with hero section (hero image may extend under navbar; no full-image color filter overlay by default)
- Standard content layout with breadcrumbs
- Global SEO setup (metadata helper, sitemap, robots.txt)
- About page
- Diving overview page
- Courses / certifications page
- Contact page
- Partners page
- Privacy & Terms pages
- Homepage refined to answer the four key questions
-
/dive-sitessection -
/plan-your-tripcontent cluster - Homepage routing destinations aligned to destination-first strategy
- Reusable BookingCTA component
- Embedded booking widget with graceful fallback
- Dedicated /book page
- Checkfront deep link CTAs on relevant pages
- Firestore integration (client-side)
- Live dive log (
/dive-log) with filtering, selection, and PDF export
- Custom 404 page
- Skip-to-content accessibility link
- 301 redirect scaffold in next.config.ts
- Structured data (JSON-LD LocalBusiness)
- Open Graph / Twitter Card metadata
- Real OG image configured
- Vercel Analytics installed
- Performance and accessibility baseline
The site is deployed on Vercel from the master branch.
Copy .env.example to .env.local and fill in the values from your Firebase Console.
Critical: the browser-side Firebase SDK requires the public variables to use the NEXT_PUBLIC_ prefix:
NEXT_PUBLIC_FIREBASE_API_KEY
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
NEXT_PUBLIC_FIREBASE_PROJECT_ID
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
NEXT_PUBLIC_FIREBASE_APP_ID
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID
NEXT_PUBLIC_SITE_URL=https://www.seasaba.com
Public read access is required for the collections used by the live site:
match /dives/{docId} { allow read: if true; }
match /boats/{docId} { allow read: if true; }
match /sites/{docId} { allow read: if true; }
match /species/{docId} { allow read: if true; }
match /guides/{docId} { allow read: if true; }
- Ensure environment variables are set in Vercel (Production + Preview)
- Merge changes to
master - Vercel builds and deploys automatically
- Verify
/dive-logloads dives and/sitemap.xmlis valid
- Check Vercel Analytics for Core Web Vitals
- Monitor Search Console for crawl errors and 301 redirect coverage
- Keep 301 redirects in
data/redirects.tspopulated from the old Wix site - Verify GA4 / GTM events fire in Google Tag Assistant or browser DevTools Network tab
The site loads Google Tag Manager in app/layout.tsx via the AnalyticsLoader component.
- Vercel Analytics: enabled independently through
@vercel/analytics/next. - Google Tag Manager: loaded only when
NEXT_PUBLIC_GTM_IDis set. - Google Analytics 4 and marketing tags: configured and loaded inside GTM. The application does not load GA4 or call
window.gtag()directly.
Tracked events are sent through the shared trackEvent, trackLinkClick, and trackBookingClick helpers in lib/analytics.ts. Each business event is pushed once to the GTM data layer and is also sent independently to Vercel Analytics.
Tracked events include:
book_now_click— Sea Saba booking CTAscheckfront_click— Checkfront fallback and direct booking linkscontact_click— internal contact CTAscontact_form_submit— contact form handoff to email or WhatsAppemail_click,phone_click,whatsapp_click— contact link clicksdirections_click— Google and Apple Maps directions linksferry_link_click— Makana Ferry and other ferry partner linkssocial_click— social media, partner, and outbound resource linkspdf_download— dive log PDF export
All events include page_location, page_path, page_title, and page_referrer. Link events also include link_url, link_text, link_domain, and outbound. Booking events include button_name, button_location, and booking_item. Legacy parameters such as link_destination, button_text, and referrer remain available for historical compatibility. Sensitive URL query strings are removed before link data is sent.
- Clarity over flash — text-first, SEO-focused content pages
- Trust over hype — calm, professional, conservation-minded tone
- Speed over spectacle — static generation, minimal JS, optimized assets
- Motion is minimal — only on homepage, degrades gracefully on mobile
- Destination over catalog — sell Saba and the Sea Saba experience first
- Understated sophistication — premium without being flashy
- Checkfront is the system of record
- Use:
- Deep links
- Embedded widgets
- Optional availability previews
- Do not move checkout logic into the website
- The website must still convert even if widgets fail
- Core marketing pages
- Diving overview pages
- Dive site pages
- Trip planning content
- FAQs and informational content
- Testimonials
- Reviews
- Staff profiles
- Dynamic operational / timely data
- Homepage hero uses a static full-bleed image
- No full-image color filter overlay by default
- Text readability should come from:
- image selection
- typography
- subtle local gradient behind text only
- light text-shadow
- Homepage may have one video background section only
- Inner pages should remain calm, stable, and text-first
The site should feel like:
Premium expedition diving on a special island.
And the brand message should feel like:
Experienced. Deliberate. Professional.