Skip to content
All work

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.

  • 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

  1. InterfaceNext.js App Router, Server Components first
  2. BFFTyped data-access layer, auth, per-route caching
  1. BackendThe platform services the BFF talks to
  2. Marketplaces1.47M listings from 32 sources
Where my work sits in Watch Pursuit, from the browser down.
  • 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.
Watch Pursuit search results for “Rolex Submariner under $10k from US sellers”, turned into Rolex, Submariner, Under 10K and US filter chips, with the filter sidebar on the left.
Plain-English search in production: the query becomes Rolex, Submariner, Under 10K and US filters.

Decisions that mattered

  1. 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.

  2. 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
    StrategyUsed where
    no-storeData that has to be fresh on every request
    revalidate / ISRPages that can be briefly stale
    cache() + request memoizationThe same fetch needed twice in one render
  3. 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.