Eliminate frontend POI waterfall for faster initial load

Listing stream fires immediately on auth without waiting for POI
fetch. POI distances are not needed for initial rendering and are
only computed when user selects POI metric or sets travel filters.
This saves ~200-500ms on initial load and keeps the stream on the
cached Redis path.
This commit is contained in:
Viktor Barzin 2026-02-22 13:29:54 +00:00
parent 3885fd52fe
commit 8ef6868881
No known key found for this signature in database
GPG key ID: 0EB088298288D958
2 changed files with 36 additions and 7 deletions

View file

@ -17,7 +17,7 @@ import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet';
import { Button } from './components/ui/button'; import { Button } from './components/ui/button';
import { Filter, Heart } from 'lucide-react'; import { Filter, Heart } from 'lucide-react';
import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature, POI, POITravelFilter } from '@/types'; import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature, POI, POITravelFilter } from '@/types';
import { refreshListings, streamListingGeoJSON, fetchUserPOIs, type StreamingProgress } from '@/services'; import { refreshListings, streamListingGeoJSON, fetchUserPOIs, fetchBulkPOIDistances, type StreamingProgress } from '@/services';
import { setOnUnauthorized } from '@/services/apiClient'; import { setOnUnauthorized } from '@/services/apiClient';
import { clearPasskeyUser } from './auth/passkeyService'; import { clearPasskeyUser } from './auth/passkeyService';
import { poiMetricPropertyName, injectPoiMetricProperty } from '@/utils/poiUtils'; import { poiMetricPropertyName, injectPoiMetricProperty } from '@/utils/poiUtils';
@ -167,7 +167,7 @@ function App() {
try { try {
for await (const batch of streamListingGeoJSON(user, parameters, (progress) => { for await (const batch of streamListingGeoJSON(user, parameters, (progress) => {
setStreamingProgress(progress); setStreamingProgress(progress);
}, { includePoiDistances: userPOIs.length > 0, signal: controller.signal })) { }, { signal: controller.signal })) {
// Deduplicate features by URL // Deduplicate features by URL
const uniqueBatch = batch.filter((feature) => { const uniqueBatch = batch.filter((feature) => {
const url = feature.properties?.url; const url = feature.properties?.url;
@ -200,7 +200,39 @@ function App() {
setStreamingProgress(null); setStreamingProgress(null);
} }
} }
}, [user, userPOIs]); }, [user]);
// Merge POI distances into listing data after both are available
useEffect(() => {
if (!user || !listingData || userPOIs.length === 0 || !queryParameters) return;
let cancelled = false;
fetchBulkPOIDistances(user, (queryParameters.listing_type as 'RENT' | 'BUY') ?? 'RENT')
.then((distanceLookup) => {
if (cancelled) return;
const updatedFeatures = accumulatedFeaturesRef.current.map(feature => {
const id = feature.properties?.id;
if (id && distanceLookup[id]) {
return {
...feature,
properties: {
...feature.properties,
poi_distances: distanceLookup[id],
},
};
}
return feature;
});
accumulatedFeaturesRef.current = updatedFeatures;
setListingData({
type: 'FeatureCollection',
features: [...updatedFeatures],
});
})
.catch(() => {}); // POI distances are best-effort
return () => { cancelled = true; };
}, [user, listingData?.features.length, userPOIs.length, queryParameters]);
// Compute processed listing data: inject synthetic POI metric property & apply max travel filter // Compute processed listing data: inject synthetic POI metric property & apply max travel filter
const processedListingData = useMemo(() => { const processedListingData = useMemo(() => {

View file

@ -69,12 +69,9 @@ export async function* streamListingGeoJSON(
user: AuthUser, user: AuthUser,
parameters: ParameterValues, parameters: ParameterValues,
onProgress?: (progress: StreamingProgress) => void, onProgress?: (progress: StreamingProgress) => void,
options?: { includePoiDistances?: boolean; signal?: AbortSignal }, options?: { signal?: AbortSignal },
): AsyncGenerator<PropertyFeature[], void, unknown> { ): AsyncGenerator<PropertyFeature[], void, unknown> {
const params = buildListingParams(parameters); const params = buildListingParams(parameters);
if (options?.includePoiDistances) {
params.include_poi_distances = 'true';
}
const queryString = buildQueryString(params); const queryString = buildQueryString(params);
const url = queryString const url = queryString
? `${API_ENDPOINTS.LISTING_GEOJSON_STREAM}?${queryString}` ? `${API_ENDPOINTS.LISTING_GEOJSON_STREAM}?${queryString}`