docs
    re-site-kit: Hot properties
    v0.6.0.md

    Hot properties

    Show the homes people are looking at most. ListingViewTracker counts a view each time someone opens a listing page, Sonor ranks the listings from those views, and getHotListings reads the ranking back for a "Trending this week" section. The same numbers power the Hot Properties view in the Sonor dashboard.

    import { getHotListings } from '@sonordev/re-site-kit/server'
    import { ListingViewTracker } from '@sonordev/re-site-kit/client'

    Count views: ListingViewTracker

    Render it on the listing detail page, as a sibling of the page content:

    <ListingViewTracker slug={listing.slug} listingKey={listing.source_listing_id} />
    PropTypeDefaultWhat it does
    slugstring | nullThe listing's slug. The preferred identifier.
    listingKeystring | nullThe MLS number (source_listing_id), used when there's no slug.
    enabledbooleantrueSet false to stop tracking, for example on a preview route.

    On mount it sends one listing_view event (category real_estate) through site-kit's analytics, with listing_slug and listing_key as properties. That means the same transport, visitor identity, batching and spam handling as every other event on the site, with no extra beacon. Sonor files these events under the Real Estate module rather than your general event log.

    A few details:

    • It renders nothing (null) and never wraps content, so it can't affect how the page renders or prerenders. Keep it that way: don't put page content inside it or inside any provider.
    • It needs site-kit analytics on the page, which SiteKitLayout gives you (see site-kit's Analytics docs). If analytics loads late, the event waits for it, so deferred analytics still catch the view.
    • It doesn't double count. A second mount for the same listing on the same path within 30 seconds (React strict mode, a quick re-render) is skipped.
    • With neither slug nor listingKey, it sends nothing.

    Read the ranking: getHotListings

    getHotListings(options?: GetHotListingsOptions): Promise<HotListingEntry[]>
    OptionTypeDefaultWhat it does
    windowstring'7d'How far back to count: '24h', '7d', '30d', or a number of hours such as '48'. The longest window is 90 days.
    limitnumber10How many listings. At most 50.
    sitestringMulti-site projects: the sub-site host.

    Only active listings are ranked. Each entry:

    FieldTypeWhat it is
    listingRealEstateListingThe listing.
    viewsnumberViews in the window.
    unique_viewersnumberDistinct visitors in the window.
    prior_viewsnumberViews in the window before this one, for comparison.
    trend_pctnumber | nullPercent change from the prior window, as a whole number (25 means up 25%). null when there's no prior data.
    last_viewed_atstring | nullWhen the listing was last viewed (ISO 8601).

    The result is cached for 5 minutes. It returns [] on any failure, and also when nothing has been viewed yet, so always handle the empty case.

    // components/TrendingHomes.tsx
    import { getHotListings } from '@sonordev/re-site-kit/server'
    import { ListingGrid } from '@sonordev/re-site-kit'
    
    export async function TrendingHomes() {
      const hot = await getHotListings({ window: '7d', limit: 6 })
      if (!hot.length) return null
    
      return (
        <section>
          <h2>Trending this week</h2>
          <ListingGrid
            listings={hot.map((entry) => entry.listing)}
            hrefFor={(listing) => (listing.slug ? `/listings/${listing.slug}` : null)}
          />
        </section>
      )
    }

    It's a server component, so the section arrives as HTML with no client JavaScript. Drop <TrendingHomes /> on the home page or a listings index.

    Showing the numbers

    To show view counts or the trend, render the cards yourself:

    // components/MostViewed.tsx
    import { getHotListings } from '@sonordev/re-site-kit/server'
    import { ListingCard } from '@sonordev/re-site-kit'
    
    export async function MostViewed() {
      const hot = await getHotListings({ window: '24h', limit: 4 })
      if (!hot.length) return null
    
      return (
        <ol className="most-viewed">
          {hot.map(({ listing, views, trend_pct }) => (
            <li key={listing.id}>
              <ListingCard listing={listing} href={listing.slug ? `/listings/${listing.slug}` : null} />
              <p>
                {views} views in the last day
                {trend_pct != null ? ` (${trend_pct > 0 ? '+' : ''}${trend_pct}%)` : ''}
              </p>
            </li>
          ))}
        </ol>
      )
    }

    A brand-new site has no views to rank, so a trending section stays hidden until people start opening listing pages. Place it where an empty space won't look odd, or fall back to searchListings({ sort: 'newest', limit: 6 }).