Skip to main content
RapidDev - Software Development Agency

How to Build Real estate listing with V0

Build a Zillow-style real estate listing app with V0 using Next.js, Supabase, and PostGIS for geospatial search. Features property listings with photo galleries, map-based search with radius filtering, saved listings, and agent inquiry forms — all in about 1-2 hours.

What you'll build

  • Property listing pages with shadcn/ui Carousel image galleries and detail sections
  • Search interface with filter sidebar using Select, Slider, and Input for price, bedrooms, and property type
  • Geospatial search using PostGIS for radius-based property discovery
  • Saved listings feature with heart toggle and dedicated saved page
  • Agent listing creation form with Supabase Storage image uploads
  • Mobile-responsive filter panel using shadcn/ui Sheet
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 Premium or higherApril 2026RapidDev Engineering Team
TL;DR

Build a Zillow-style real estate listing app with V0 using Next.js, Supabase, and PostGIS for geospatial search. Features property listings with photo galleries, map-based search with radius filtering, saved listings, and agent inquiry forms — all in about 1-2 hours.

What you're building

Real estate apps need to display properties beautifully, search efficiently by location and features, and connect buyers with agents. Whether you are building a local MLS alternative or a niche property marketplace, the core features are the same — listings, search, photos, and inquiries.

V0 generates the listing pages, search interface, and image gallery from prompts. Supabase with PostGIS handles geospatial queries like finding properties within 5 miles of a location. Supabase Storage handles property photo uploads.

The architecture uses Next.js App Router with Server Components for listing pages (great for SEO), an API route for complex geospatial search queries, Server Actions for saving listings and submitting inquiries, and Supabase Storage for property images with public bucket access.

Final result

A searchable real estate listing platform with property cards, photo galleries, map-based geospatial search, saved listings, agent inquiry forms, and a listing creation interface for agents.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase
PostGISGeospatial Queries

Prerequisites

  • A V0 account (Premium recommended for multiple page types)
  • A Supabase project with PostGIS extension enabled (free tier works)
  • Sample property data or photos for testing
  • Basic understanding of real estate listing concepts

Build steps

1

Set up the project and listing schema with PostGIS

Open V0 and create a new project. Use the Connect panel to add Supabase. Enable PostGIS for geospatial queries and create the schema for listings, images, saved listings, and inquiries.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a real estate listing app. Create a Supabase schema with PostGIS enabled:
3// 1. listings: id (uuid PK), agent_id (uuid FK), title (text), description (text), address (text), city (text), state (text), zip (text), latitude (numeric), longitude (numeric), price (integer), bedrooms (integer), bathrooms (numeric), sqft (integer), property_type (text check house/condo/townhouse/land), status (text check active/pending/sold), features (text[]), created_at (timestamptz), updated_at (timestamptz)
4// 2. listing_images: id (uuid PK), listing_id (uuid FK), image_url (text), position (integer), created_at (timestamptz)
5// 3. saved_listings: id (uuid PK), user_id (uuid FK), listing_id (uuid FK), created_at (timestamptz) with unique constraint on (user_id, listing_id)
6// 4. inquiries: id (uuid PK), listing_id (uuid FK), user_id (uuid FK nullable), name (text), email (text), message (text), created_at (timestamptz)
7// Enable PostGIS extension. Create a spatial index on listings (latitude, longitude).
8// RLS: anyone can SELECT active listings, only agents can INSERT/UPDATE their own.
9// Generate SQL migration and TypeScript types.

Expected result: Supabase is connected with PostGIS enabled. All four tables are created with a spatial index on listing coordinates. RLS allows public read access for active listings.

2

Build the search page with filters and listing cards

Create the main search page with a filter sidebar and a grid of listing cards. Filters include price range, bedrooms, bathrooms, and property type. On mobile, filters collapse into a Sheet.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a real estate search page at app/page.tsx.
3// Requirements:
4// - Two-column layout: filter sidebar (left, 280px) and listing grid (right)
5// - Filter sidebar with:
6// - Input for location/city search
7// - Slider for price range (min/max)
8// - Select for bedrooms (Any, 1+, 2+, 3+, 4+)
9// - Select for bathrooms (Any, 1+, 2+, 3+)
10// - Select for property type (All, House, Condo, Townhouse, Land)
11// - Badge chips showing active filters with X to remove
12// - On mobile: filters collapse into a Sheet triggered by a filter Button
13// - Listing grid: shadcn/ui Card for each listing showing:
14// - Image (first listing_image), Skeleton while loading
15// - Price formatted as currency, bedrooms/bathrooms/sqft
16// - Badge for status (active=green, pending=yellow, sold=red)
17// - Heart icon Button for save/unsave
18// - Card is clickable → navigates to /listings/[id]
19// - Filters update URL searchParams for shareable filtered URLs
20// - Server Components for initial data, 'use client' for filter interactions
21// - Use Supabase .textSearch() for location search

Pro tip: Use V0's Design Mode (Option+D) to visually adjust the listing card grid layout, image aspect ratios, and search bar positioning for free.

Expected result: A search page with a filter sidebar (Sheet on mobile), listing card grid with images and price, and URL-synced filters for shareable search results.

3

Create the listing detail page with image gallery

Build the individual listing page showing the full property details, a Carousel image gallery, features list, and an inquiry form for contacting the agent.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a listing detail page at app/listings/[id]/page.tsx.
3// Requirements:
4// - Fetch listing with images and agent info from Supabase
5// - Top section: shadcn/ui Carousel for image gallery with thumbnails below
6// - Use Skeleton for image loading states
7// - Detail section with two columns:
8// - Left: price (large), address, bedrooms/bathrooms/sqft row, description paragraph
9// - Right: Agent Card with Avatar and name, inquiry form with Input for name/email, Textarea for message, Button to submit
10// - Features section: list of features as Badge components in a flex wrap
11// - Status Badge at top (active/pending/sold)
12// - Heart Button to save/unsave listing (Server Action toggleFavorite)
13// - Server Action submitInquiry() for the contact form
14// - Use Server Components for SEO-optimized data fetching
15// - Generate proper metadata for the listing title and description

Expected result: A listing detail page with an image Carousel, property details with formatted price and features, an agent contact form, and a save/unsave heart button.

4

Add geospatial search with PostGIS

Build an API route that uses PostGIS to find properties within a radius of a given location. This enables "properties near me" and "properties within 5 miles of downtown" searches.

app/api/listings/search/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function GET(req: NextRequest) {
10 const { searchParams } = new URL(req.url)
11 const lat = parseFloat(searchParams.get('lat') ?? '0')
12 const lng = parseFloat(searchParams.get('lng') ?? '0')
13 const radiusMiles = parseFloat(searchParams.get('radius') ?? '10')
14 const radiusMeters = radiusMiles * 1609.34
15 const minPrice = parseInt(searchParams.get('min_price') ?? '0')
16 const maxPrice = parseInt(searchParams.get('max_price') ?? '999999999')
17 const bedrooms = parseInt(searchParams.get('bedrooms') ?? '0')
18
19 const { data, error } = await supabase.rpc('search_listings_nearby', {
20 search_lat: lat,
21 search_lng: lng,
22 radius_meters: radiusMeters,
23 min_price: minPrice,
24 max_price: maxPrice,
25 min_bedrooms: bedrooms,
26 })
27
28 if (error) {
29 return NextResponse.json({ error: error.message }, { status: 500 })
30 }
31
32 return NextResponse.json({ listings: data })
33}

Pro tip: Create the search_listings_nearby Supabase RPC function with: SELECT *, ST_Distance(ST_MakePoint(longitude, latitude)::geography, ST_MakePoint(search_lng, search_lat)::geography) as distance_meters FROM listings WHERE ST_DWithin(...) and price filters, ORDER BY distance_meters.

Expected result: GET /api/listings/search?lat=40.7&lng=-74.0&radius=5 returns listings within 5 miles of the coordinates, sorted by distance, with price and bedroom filters applied.

5

Build the listing creation form with image uploads

Create a form where agents can post new listings with property details and upload multiple images to Supabase Storage.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a listing creation page at app/listings/new/page.tsx.
3// Requirements:
4// - Protected by auth (only agents can access)
5// - Form sections using shadcn/ui Card:
6// - Basic Info: Input for title, Textarea for description, Select for property_type
7// - Location: Input for address, city, state, zip (auto-geocode to lat/lng)
8// - Details: Input for price, bedrooms, bathrooms, sqft
9// - Features: ToggleGroup for common features (pool, garage, fireplace, etc) plus custom Input
10// - Images: drag-and-drop upload zone for multiple images, preview thumbnails, reorder by dragging
11// - Images upload to Supabase Storage public bucket 'listing-images'
12// - On submit: Server Action creates listing, uploads images, creates listing_images records
13// - Show Progress during upload
14// - After creation, redirect to the new listing page
15// - 'use client' for the interactive form with file uploads

Expected result: A multi-section listing creation form with drag-and-drop image uploads to Supabase Storage. Submitting creates the listing and redirects to its detail page.

Complete code

app/api/listings/search/route.ts
1import { NextRequest, NextResponse } from 'next/server'
2import { createClient } from '@supabase/supabase-js'
3
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_SERVICE_ROLE_KEY!
7)
8
9export async function GET(req: NextRequest) {
10 const { searchParams } = new URL(req.url)
11 const lat = parseFloat(searchParams.get('lat') ?? '0')
12 const lng = parseFloat(searchParams.get('lng') ?? '0')
13 const radiusMiles = parseFloat(searchParams.get('radius') ?? '10')
14 const minPrice = parseInt(searchParams.get('min_price') ?? '0')
15 const maxPrice = parseInt(searchParams.get('max_price') ?? '999999999')
16 const bedrooms = parseInt(searchParams.get('bedrooms') ?? '0')
17 const propertyType = searchParams.get('type')
18
19 let query = supabase
20 .from('listings')
21 .select('*, listing_images(image_url, position)')
22 .eq('status', 'active')
23 .gte('price', minPrice)
24 .lte('price', maxPrice)
25 .gte('bedrooms', bedrooms)
26
27 if (propertyType && propertyType !== 'all') {
28 query = query.eq('property_type', propertyType)
29 }
30
31 if (lat && lng) {
32 const { data, error } = await supabase.rpc(
33 'search_listings_nearby',
34 {
35 search_lat: lat,
36 search_lng: lng,
37 radius_meters: radiusMiles * 1609.34,
38 min_price: minPrice,
39 max_price: maxPrice,
40 min_bedrooms: bedrooms,
41 }
42 )
43 if (error) {
44 return NextResponse.json({ error: error.message }, { status: 500 })
45 }
46 return NextResponse.json({ listings: data })
47 }
48
49 const { data, error } = await query
50 .order('created_at', { ascending: false })
51 .limit(50)
52
53 if (error) {
54 return NextResponse.json({ error: error.message }, { status: 500 })
55 }
56 return NextResponse.json({ listings: data })
57}

Customization ideas

Add interactive map view

Integrate react-map-gl or Google Maps to display listings as map pins with popups showing price and thumbnail. Filter listings by map viewport bounds.

Build a mortgage calculator

Add a mortgage calculator component on each listing page that shows estimated monthly payments based on price, down payment, interest rate, and loan term.

Add virtual tour support

Embed 3D virtual tours from Matterport or 360-degree photos directly on listing pages for remote property viewing.

Build agent profiles

Create agent profile pages showing their listings, contact info, reviews, and transaction history to build trust with buyers.

Common pitfalls

Pitfall: Not enabling PostGIS extension before running spatial queries

How to avoid: Enable PostGIS in Supabase Dashboard: Database > Extensions > search PostGIS > Enable. Then create the spatial index on listing coordinates.

Pitfall: Uploading listing images to a private Supabase Storage bucket

How to avoid: Create a public bucket named listing-images in Supabase Storage. Set a bucket policy allowing public SELECT. Store the public URL in listing_images.image_url.

Pitfall: Fetching all listings on the search page without pagination

How to avoid: Use Supabase .range() for pagination with a limit of 20-50 listings per page. Add a Load More button or infinite scroll.

Best practices

  • Use Server Components for listing detail pages to optimize SEO — search engines index the server-rendered HTML
  • Store listing images in a Supabase Storage public bucket so URLs are permanently accessible without signing
  • Set NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in the Vars tab (both are publishable keys, so NEXT_PUBLIC_ is correct)
  • Use V0's Design Mode to visually adjust the listing card grid layout and image aspect ratios for free
  • Create a spatial index on listing coordinates for fast geospatial queries at scale
  • Use URL searchParams for filter state so filtered search results are shareable and bookmarkable

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a real estate listing app with Next.js App Router, Supabase, and PostGIS. Write a Supabase RPC function called search_listings_nearby that accepts search_lat, search_lng, radius_meters, min_price, max_price, and min_bedrooms. It should use ST_DWithin for the radius filter and ST_Distance for sorting by proximity. Return all listing columns plus distance_meters. Include the CREATE INDEX for the spatial index.

Build Prompt

Create a property search filter component with shadcn/ui. Include Input for location, Slider for price range ($0 - $2M), Select for bedrooms (Any/1+/2+/3+/4+), Select for property type (All/House/Condo/Townhouse/Land), and Badge chips for active filters with X to remove. On mobile, wrap in a Sheet. Sync all filter values with URL searchParams using useSearchParams and useRouter.

Frequently asked questions

Do I need a paid V0 plan for a real estate listing app?

V0 Free works for the basic build, but Premium ($20/month) is recommended because the app has multiple page types (search, listing detail, creation form) that benefit from prompt queuing.

How does the geospatial search work?

PostGIS is a PostgreSQL extension that adds geographic functions. The search API uses ST_DWithin to find listings within a radius and ST_Distance to sort by proximity. Enable PostGIS in your Supabase Dashboard under Extensions.

Can I add an interactive map with property pins?

Yes. Add react-map-gl or @vis.gl/react-google-maps to display listings as map markers. Set NEXT_PUBLIC_GOOGLE_MAPS_KEY or NEXT_PUBLIC_MAPBOX_TOKEN in the Vars tab (publishable keys, so NEXT_PUBLIC_ is correct).

How do I handle property image uploads?

Images upload to a Supabase Storage public bucket. The public URL is stored in the listing_images table. Use the Supabase Storage SDK in a Server Action to handle the upload and return the public URL.

How do I deploy the listing app?

Click Share then Publish to Production in V0. Your listing pages are server-rendered for SEO. Set Supabase credentials via the Connect panel and any map API keys in the Vars tab.

Can RapidDev help build a custom real estate platform?

Yes. RapidDev has built 600+ apps including real estate platforms with MLS integrations, mortgage calculators, and agent management systems. Book a free consultation to discuss your requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.