Case study 02, Ontik Technology
Watch Pursuit
A watch marketplace aggregator: 1.47M listings from 32 marketplaces behind one search. I architected and built the server-first Next.js frontend and its BFF (the API layer between the site and the backend), and I own the frontend decisions on a team of four engineers.
- Live site (opens in a new tab)production server
1,471,473
listings from 32 marketplaces, served through the frontend and BFF I architected
50+
filters on one discovery page, plus plain-English search
The problem
Put 1.47M listings from 32 marketplaces behind one fast, filterable interface, while four engineers build features in the same codebase at the same time.
My part
I own the frontend architecture: how the App Router is structured, the BFF layer, the component library and the conventions everyone builds against. I also review and refactor my teammates’ code.
What I built
- InterfaceNext.js App Router, Server Components first
- BFFTyped data-access layer, auth, per-route caching
- BackendThe platform services the BFF talks to
- Marketplaces1.47M listings from 32 sources
- Discovery with 50+ filters coordinated through Zustand, debounced server-side search and infinite loading.
- Natural-language search: “Rolex Submariner under $10k from US sellers” becomes brand, model, price and location filters (screenshot below, running in production).
- Auth end to end: access and refresh tokens in HTTP-only cookies, silent refresh, middleware-guarded dashboard routes, Google login and session persistence.
- Less JavaScript in the browser: client components only where interaction needs them, plus code splitting, lazy loading and image optimization.

Decisions that mattered
One typed API layer, not one per feature
Auth headers, pagination, filtering, sorting and the 401 refresh flow live in one centralized, typed data-access layer. Any engineer can add a feature against the same contract instead of calling the backend their own way.
Cache per route, not globally
Each route gets the caching it actually needs, instead of one setting for the whole app.
Caching strategy by route Strategy Used where no-store Data that has to be fresh on every request revalidate / ISR Pages that can be briefly stale cache() + request memoization The same fetch needed twice in one render Conventions that people and AI agents both follow
I broke the design system down into a component library with shared naming, prop and folder conventions, then split feature work across the team against it. I wired Figma to components through MCP and wrote our Next.js and shadcn/ui conventions into shared instruction files, so AI-assisted work landed consistently enough to merge, and the team hit its delivery dates.
