A Next.js site with the App Router. The widget's elements sit in a client component. The car types' facts, such as names and descriptions, render on the server with the rental's secret key, so search engines see them.
1. Keys and the script
Put the keys in .env.local. The browser key is public. The secret key is an sk_live_ key of the rental's Website channel, from Settings · API keys in Mellow, with fleet.read. It stays on the server.
auth holds the browser key. React keeps key for itself, so the widget reads its key from auth. onmellow-booked listens for the mellow-booked event: React 19 takes a prop that starts with on as a listener on a custom element. With the type files, event.detail has its type.
3. Facts on the server
The page is a server component. It reads the car types from the API with the secret key, and puts the client component below them.
description is short Markdown. Draw only paragraphs, lists, bold, italic and links from it.
One page for each step
To give each step its own address, put the client component in a layout that every booking page shares. It then stays in the page across the moves, and Next's router takes each move with no full page load:
Code
// app/book/layout.tsximport { Booking } from "./booking";export default function BookLayout({ children }: { children: React.ReactNode }) { return ( <main> {children} <Booking /> </main> );}
Make a page for each path, such as app/book/insurance/page.tsx. A page may return null. Then add paths and the router to the client component:
Code
// app/book/booking.tsx"use client";import { useEffect, useRef } from "react";import { usePathname, useRouter } from "next/navigation";type Root = HTMLElement & { storeReady: Promise<{ locate(): void }> };const PATHS = "car-type=/book insurance=/book/insurance driver=/book/details pay=/book/pay done=/book/thanks";export function Booking() { const ref = useRef<Root>(null); const router = useRouter(); const pathname = usePathname(); // After the router moved, the store reads the new address. useEffect(() => { customElements .whenDefined("mellow-booking") .then(() => ref.current?.storeReady) .then((store) => store?.locate()); }, [pathname]); return ( <mellow-booking ref={ref} rental="your-rental" auth={process.env.NEXT_PUBLIC_MELLOW_KEY!} paths={PATHS} onmellow-step={(event) => { // The next step is on another page: move with Next's router, not a full page load. const next = new URL(event.detail.path, location.href); if (next.pathname === location.pathname) return; event.preventDefault(); router.push(next.pathname + next.search); }} /> );}
Tried: on 2026-10-20, with Next.js 16.4 and React 19.3 on localhost, against a local Mellow with the test key and a local sk_live_ key: a <mellow-search> on the home page, the facts from the server, the flow from the car types to "Test booking" on /book/thanks with every move by Next's router, the onmellow-booked listener, and next build with the type check.