Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ozcaar/real-estate-template/llms.txt

Use this file to discover all available pages before exploring further.

The Property interface is the core domain model of the template. Every listing card, detail page, sitemap entry, and JSON-LD block is derived from this shape. The interface lives at app/features/properties/types/property.types.ts and is intentionally backend-friendly: the same fields map cleanly to a typical CMS or REST API response, so the static MVP data can be swapped for a real data source later without touching any component. A matching Zod schema (propertySchema) validates every record at module load, ensuring that structural errors are caught before they reach the UI.

Union types

Three union types constrain the enumerable fields of Property.

PropertyOperationType

export type PropertyOperationType = 'sale' | 'rent'
ValueMeaning
'sale'Property is listed for purchase
'rent'Property is listed for rental

PropertyType

export type PropertyType =
  | 'house'
  | 'apartment'
  | 'land'
  | 'commercial'
  | 'office'
ValueMeaning
'house'Detached or semi-detached residential home
'apartment'Unit within a multi-unit building
'land'Bare land or lot without a structure
'commercial'Retail, warehouse, or mixed-use commercial space
'office'Dedicated office space

PropertyStatus

export type PropertyStatus =
  | 'available'
  | 'sold'
  | 'rented'
  | 'reserved'
  | 'hidden'
ValueMeaning
'available'Actively listed and visible to the public
'sold'Sale completed; excluded from active listings but still visible
'rented'Rental occupied; excluded from active listings but still visible
'reserved'Under offer or contract; still shown as related listings
'hidden'Completely excluded from all public views — getAll, filter, and the sitemap
Properties with status: 'hidden' are filtered out in propertiesService.getAll() before any other logic runs. They will never appear in search results, featured sections, related listings, or the XML sitemap. Use 'hidden' to soft-delete a record or temporarily suppress a listing without removing it from the data file.

PropertyCoordinates interface

Used by the optional coordinates field on Property to pin a listing on a map.
lat
number
required
Latitude in decimal degrees. Positive values are north of the equator; negative values are south.
lng
number
required
Longitude in decimal degrees. Positive values are east of the prime meridian; negative values are west.

Property interface

id
string
required
Unique listing identifier. Must be a non-empty string. Used as a stable sort tiebreaker by propertiesService — every sort branch calls a.id.localeCompare(b.id) to guarantee deterministic order across SSR and CSR.
title
string
required
Human-readable listing title shown on cards and the detail page heading. Agency content — not an i18n key.
slug
string
required
URL-safe identifier used in the href of the detail route: /properties/[slug]. Must be unique across the catalog. The detail page does a propertiesService.getBySlug(slug) lookup and returns a 404 if the property is hidden or missing.
description
string
required
Full marketing description rendered on the property detail page. Supports plain text; markdown rendering depends on the component consuming the field.
operationType
PropertyOperationType
required
Whether the listing is for 'sale' or 'rent'. Maps directly to the ?operation= query parameter on the /properties listing page.
propertyType
PropertyType
required
Category of the property. Maps directly to the ?type= query parameter on the /properties listing page.
price
number
required
Listing price in the currency specified by currency. Must be a non-negative number (Zod enforces z.number().nonnegative()). Formatted at render time by the currency-format.ts utility using the agency’s ISO 4217 currency code.
currency
string
required
ISO 4217 currency code for this specific listing (e.g. 'USD', 'MXN'). Falls back to the agency currency when components need a display currency and this field is unavailable.
location
string
required
Human-readable neighborhood or area name (e.g. 'Polanco', 'Downtown'). Included in the accent-insensitive substring search performed by propertiesService.filter against the ?location= query param.
city
string
required
City where the property is located. Also included in the accent-insensitive location search. Used by getRelated to score a +2 bonus for same-city matches.
state
string
required
State or province. Included in location search.
country
string
required
Country. Included in location search. Used by getRelated to score a +1 bonus for same-country matches when city did not already match.
bedrooms
number
Number of bedrooms. Optional. Zod validates as z.number().int().nonnegative() when present.
bathrooms
number
Number of bathrooms. Optional. Zod validates as z.number().nonnegative() when present (allows half-baths such as 1.5).
parkingSpaces
number
Number of dedicated parking spaces. Optional. Zod validates as z.number().int().nonnegative() when present.
sizeUnit
'metric' | 'imperial'
Unit for constructionSize and landSize. When omitted, components fall back to the agency measurementUnit. The number is rendered as-is in the declared unit — the template performs no automatic m² ↔ ft² conversion. Set this per record when an agency mixes units in the same catalog.
constructionSize
number
Built floor area, expressed in sizeUnit. Optional. Zod validates as z.number().nonnegative() when present.
landSize
number
Total lot/land area, expressed in sizeUnit. Optional. Zod validates as z.number().nonnegative() when present.
images
string[]
required
Array of image paths served from public/. All items must be non-empty strings (Zod enforces z.array(z.string().min(1))). The first item is typically the same as coverImage.
coverImage
string
required
Primary image path used as the LCP candidate on the detail page and the card thumbnail. Zod enforces z.string().min(1) — an empty string would produce a broken <img> before any consumer code could catch it.
amenities
string[]
required
List of amenity labels (e.g. ['Pool', 'Gym', 'Rooftop terrace']). Agency content strings, not i18n keys. All items must be non-empty strings.
developmentId
string
Optional reference to a Development.id. Links the property to its parent development project. getRelated awards a +2 score bonus to sibling units sharing the same developmentId.
agentId
string
Optional reference to an Agent.id. Associates the listing with a specific team member. getRelated awards a +1 score bonus when both properties share the same agent.
coordinates
PropertyCoordinates
Optional { lat, lng } pair for map rendering. See PropertyCoordinates above.
status
PropertyStatus
required
Current listing status. See PropertyStatus above for visibility rules.
When true, the property is surfaced in homepage showcases via propertiesService.getFeatured() and ranked first in the default 'featured' sort order on the listing page.

Zod schema

The file app/features/properties/schemas/property.schema.ts provides a runtime mirror of the Property interface. The hand-written interface remains the canonical TypeScript type; the schema is the runtime boundary that validates external data (static MVP data today, a CMS or API response later) before it reaches services and components.

propertySchema

Validates a single property record. Key Zod rules beyond type checking:
  • id, title, slug, description, currency, location, city, state, country, coverImage.min(1) (non-empty string)
  • price, constructionSize, landSize.nonnegative()
  • bedrooms, parkingSpaces.int().nonnegative()
  • bathrooms.nonnegative() (allows fractional values)
  • images, amenitiesz.array(z.string().min(1))
  • operationType, propertyType, status, sizeUnitz.enum([...]) matching their TypeScript unions
import { z } from 'zod'

export const propertySchema = z.object({
  id: z.string().min(1),
  title: z.string().min(1),
  slug: z.string().min(1),
  description: z.string().min(1),
  operationType: propertyOperationTypeSchema,
  propertyType: propertyTypeSchema,
  price: z.number().nonnegative(),
  currency: z.string().min(1),
  location: z.string().min(1),
  city: z.string().min(1),
  state: z.string().min(1),
  country: z.string().min(1),
  bedrooms: z.number().int().nonnegative().optional(),
  bathrooms: z.number().nonnegative().optional(),
  parkingSpaces: z.number().int().nonnegative().optional(),
  sizeUnit: propertySizeUnitSchema.optional(),
  constructionSize: z.number().nonnegative().optional(),
  landSize: z.number().nonnegative().optional(),
  images: z.array(z.string().min(1)),
  coverImage: z.string().min(1),
  amenities: z.array(z.string().min(1)),
  developmentId: z.string().optional(),
  agentId: z.string().optional(),
  coordinates: propertyCoordinatesSchema.optional(),
  status: propertyStatusSchema,
  featured: z.boolean(),
})

propertyListSchema

Validates the full data array. Defined as z.array(propertySchema). Parsed at module load in app/features/properties/data/properties.tsany invalid record causes a ZodError and prevents the application from booting. Fix the offending record in the data file to resolve it.
export const propertyListSchema = z.array(propertySchema)
A compile-time guard keeps the inferred schema type and the hand-written interface structurally identical:
type PropertyInput = z.infer<typeof propertySchema>

const _typeCheck: PropertyInput extends Property
  ? Property extends PropertyInput
    ? true
    : never
  : never = true
If property.types.ts and property.schema.ts drift, this guard fails to compile before any test or runtime check runs.

PropertySort type and isPropertySort guard

Defined in app/features/properties/services/properties.service.ts alongside PropertyFilters.
export type PropertySort = 'featured' | 'price-asc' | 'price-desc'
ValueSort order
'featured' (default)featured: true first, then id ascending for ties
'price-asc'price ascending, then id ascending for ties
'price-desc'price descending, then id ascending for ties
Every sort uses id.localeCompare(otherId) as a tiebreaker — equal-scoring or equal-priced properties always render in the same order across SSR and CSR, preventing hydration mismatches.

isPropertySort type guard

export function isPropertySort(value: unknown): value is PropertySort {
  return typeof value === 'string' && (VALID_SORTS as readonly string[]).includes(value)
}
Use this guard in pages to coerce a raw route.query.sort value (which is string | string[] | null | undefined) into a safe PropertySort before passing it to propertiesService.filter. Unknown sort values are coerced to 'featured'.

PropertyFilters interface

export interface PropertyFilters {
  operation?: string
  type?: string
  location?: string
}
operation
string
Matches Property.operationType with a case-insensitive exact comparison. Pass 'sale' or 'rent'. Undefined means no filter on this criterion.
type
string
Matches Property.propertyType with a case-insensitive exact comparison. Pass any PropertyType value. Undefined means no filter on this criterion.
location
string
Free-text substring match against location + city + state + country joined with spaces. The search is case-insensitive and accent-insensitive (Unicode NFD decomposition + combining-mark strip — no external dependency). This means 'Mexico' matches 'México', 'Queretaro' matches 'Querétaro', and 'Leon' matches 'Nuevo León'. Undefined means no filter on this criterion.

Build docs developers (and LLMs) love