Skip to main content

Home · Projects · Web product

BNBComps

Short-term rental comps UI: URL filters, pruned payloads, short-lived cache.

Role
Frontend engineering
Type
Analytics UI over HTTP APIs
Client
BNBComps
Status
Live
Next.jsTypeScriptAPITailwind
BNBComps rental analytics platform screenshot

Outcome

Made comparable search usable under expensive aggregations by pruning list fields, debouncing, and caching identical filter hashes in the client.

Problem

Hosts and investors needed neighborhood-level STR comps. Existing tools were generic or too slow once filters stacked. Backend aggregations were expensive; the UI could not refetch a full payload on every toggle.

Solution

Filters in the URL where shareability mattered. Debounced API calls. Normalized DTOs so the table layer does not re-parse ad hoc JSON. Progressive detail: aggregates first, row expansion or a second fetch for deep comps.

Architecture
Frontend
Next.js + TypeScriptFilter toolbar · data tables
Cache
In-memory / short-TTL filter cache
API
Existing STR analytics HTTP APIs
Frontend
Next.js UI. Layout vs data hooks split so table logic stays testable.
APIs
Read-heavy JSON. Parameters for geography, bedrooms, date range. Owned by the product backend — this work consumed them.
Databases
No database owned in this engagement; analytics live behind the HTTP APIs.
Key features
  • Multi-axis filters as query params; list endpoints return only columns the grid needs
  • Comparable view composed from the same row model (no second fetching stack)
  • Time-bucketed metrics when the API exposes them; empty/partial series handled in the client
  • Saved filter sets in local persistence (not cross-device unless an account API exists)
  • Stable keys / memoized selectors so re-filtering does not remount tables
Engineering challenges

Challenge

Heavy queries made the UI feel stuck.

Decision

Contract the list payload and cache by filter hash.

Implementation

Narrow fields, page/limit, debounce, in-memory or short-TTL cache keyed by the filter hash.

Result

Revisiting a recent view often skips a cold round trip. Freshness is the tradeoff.

Challenge

Raw metrics were hard to scan.

Decision

Lead with summaries; keep detail behind expansion.

Implementation

Headline aggregates first, then row-level comps on demand.

Result

Pricing decisions start from a readable summary instead of a dense grid dump.

Technology decisions

Client cache keyed by filter hash

Users oscillate between two geographies. Identical requests should not hit expensive aggregations twice in a session.

Alternative. Always refetch, or a shared Redis cache on the API.

Tradeoff. Client cache is session-scoped. It does not help the first visitor or other devices.

URL-encoded filters

Shared links need to restore the same comparable view.

Alternative. Filters only in React state.

Tradeoff. Ugly URLs; worth it for shareability.

My ownership
  • BuiltNext.js comparable UI: filters, tables, progressive detail, client cache
  • BuiltTyped DTOs and data hooks against the analytics HTTP API
  • ExistingSTR analytics HTTP APIs and data pipeline behind them
  • Frontend engineering engagement — not a claim of owning the analytics warehouse.
Results

Comparable exploration stays responsive relative to uncached full payloads. No published conversion or revenue metrics.