Real estate platforms live or die by how fast their listing pages feel. If filtering by price, location, or property type takes a beat too long, users bounce before they ever see what you actually built. When building the listings experience for a real estate platform, most of the engineering effort went into making search and filtering feel instant — not adding more features.
Start with the data shape, not the UI
Before touching a single component, it's worth deciding how filters map to your data layer. Filtering client-side works fine for a few hundred listings, but once you're dealing with thousands of properties, you want filtering to happen at the query level — in the database or API — not by shipping every record to the browser and filtering in JavaScript.
For a Next.js app, this usually means:
- Filters live in the URL as search params, so they're shareable and bookmarkable
- Server Components fetch already-filtered data based on those search params
- Client-side state only handles the in-progress filter UI (what the user is currently selecting), not the actual filtering logic
This split matters more than it sounds. It means the expensive work — querying, filtering, sorting — happens server-side, and the client only ever renders what it's given.
Image loading is the real bottleneck
On a listings page, images are almost always the slowest part of the render, not the data. A grid of property photos, each one potentially several megabytes unoptimized, will tank your Largest Contentful Paint score faster than any amount of JavaScript will.
A few things that consistently help:
- Use
next/imagewith explicitsizes— letting the browser request an appropriately sized image instead of a full-resolution one it'll just scale down. - Lazy-load anything below the fold — the first few listing cards should load eagerly, everything else can wait until it's near the viewport.
- Reserve layout space with aspect-ratio containers — this avoids layout shift as images pop in, which matters both for UX and for Core Web Vitals.
Debounce, but don't over-debounce
Text-based filters (like a location search box) need debouncing so you're not firing a request on every keystroke. But there's a balance — debounce too aggressively and the UI feels sluggish; too little and you're hammering the backend. Somewhere in the 250–400ms range tends to feel responsive without being wasteful.
For filters that are just toggles or dropdowns (property type, price range), you don't need debouncing at all — fire the update immediately, since there's no rapid-fire input to worry about.
The filter UI shouldn't block the current results
One subtle UX detail: when a user changes a filter, don't blank the page while the new results load. Keep the previous results visible with a subtle loading state layered on top. Nothing makes an app feel slower than a flash of empty state on every interaction, even if the actual query is fast.
Takeaway
None of this is exotic — it's mostly about being deliberate with where computation happens (server vs. client) and treating image loading as a first-class performance concern rather than an afterthought. For a listings-heavy product, getting these fundamentals right does more for perceived speed than almost any other optimization.