Skip to content
intermediate 25 minutes

Next.js Integration Guide

Build a property search application with Next.js using server components, API routes, and Incremental Static Regeneration powered by BayutAPI.

What you'll build: A Next.js property search app with SSR, API routes, and ISR
Prerequisites: Next.js 14+ · Node.js 18+ · RapidAPI account (free) · Basic Next.js knowledge

Prerequisites

  • Node.js 18+ installed
  • A RapidAPI account with a BayutAPI subscription (get your free API key)
  • Familiarity with Next.js App Router and React Server Components

Installation

Create a new Next.js project:

npx create-next-app@latest my-property-app --app --typescript
cd my-property-app

Add your API key to .env.local:

RAPIDAPI_KEY=your_api_key_here

Authentication Setup

Create a server-side API utility. Since this runs on the server, your API key stays secure:

// src/lib/bayut-api.ts
const BASE_URL = "https://bayut14.p.rapidapi.com";

const headers = {
  "x-rapidapi-key": process.env.RAPIDAPI_KEY!,
  "x-rapidapi-host": "bayut14.p.rapidapi.com",
};

export async function searchProperties(params: Record<string, string>) {
  const queryString = new URLSearchParams(params).toString();
  const response = await fetch(`${BASE_URL}/search-property?${queryString}`, {
    headers,
    next: { revalidate: 3600 }, // Cache for 1 hour (ISR)
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  return response.json();
}

export async function autocomplete(query: string) {
  const params = new URLSearchParams({ query, langs: "en" });
  const response = await fetch(`${BASE_URL}/autocomplete?${params}`, {
    headers,
    next: { revalidate: 86400 }, // Cache for 24 hours
  });

  if (!response.ok) {
    throw new Error(`Autocomplete failed: ${response.status}`);
  }

  return response.json();
}

Basic Example: Server Component Property Page

Fetch and render properties entirely on the server — no client-side JavaScript needed for the initial render:

// src/app/properties/page.tsx
import { searchProperties } from "@/lib/bayut-api";

interface Property {
  id: number;
  title: string;
  price: number;
  bedrooms: number;
  bathrooms: number;
  area: number;
  location: string;
}

export default async function PropertiesPage({
  searchParams,
}: {
  searchParams: Promise<{ page?: string; location?: string }>;
}) {
  const params = await searchParams;
  const page = params.page || "1";
  const locationId = params.location || "5002";

  const data = await searchProperties({
    purpose: "for-sale",
    location_ids: locationId,
    page,
  });

  const properties: Property[] = data.data.properties;
  const { total, totalPages } = data.data;

  return (
    <main className="max-w-6xl mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-2">Properties for Sale</h1>
      <p className="text-gray-400 mb-8">{total.toLocaleString()} properties found</p>

      <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
        {properties.map((property) => (
          <div key={property.id} className="border border-gray-700 rounded-lg p-4">
            <h2 className="font-semibold text-lg">{property.title.en}</h2>
            <p className="text-2xl font-bold mt-2">
              AED {property.price.toLocaleString()}
            </p>
            <p className="text-gray-400 mt-1">
              {property.bedrooms} bed &middot; {property.bathrooms} bath &middot; {property.area} sqft
            </p>
            <p className="text-gray-500 text-sm mt-1">{property.location}</p>
          </div>
        ))}
      </div>

      <div className="flex justify-center gap-4 mt-8">
        {Number(page) > 1 && (
          <a href={`/properties?page=${Number(page) - 1}&location=${locationId}`}
             className="px-4 py-2 border rounded">
            Previous
          </a>
        )}
        <span className="px-4 py-2">Page {page} of {totalPages}</span>
        {Number(page) < totalPages && (
          <a href={`/properties?page=${Number(page) + 1}&location=${locationId}`}
             className="px-4 py-2 border rounded">
            Next
          </a>
        )}
      </div>
    </main>
  );
}

API Route for Client-Side Calls

Create an API route to proxy requests from client components. This keeps your API key on the server:

// src/app/api/properties/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);

  const params = new URLSearchParams();
  params.set("purpose", searchParams.get("purpose") || "for-sale");
  params.set("page", searchParams.get("page") || "1");

  const locationId = searchParams.get("location_ids");
  if (locationId) params.set("location_ids", locationId);

  const minPrice = searchParams.get("price_min");
  if (minPrice) params.set("price_min", minPrice);

  const maxPrice = searchParams.get("price_max");
  if (maxPrice) params.set("price_max", maxPrice);

  try {
    const response = await fetch(
      `https://bayut14.p.rapidapi.com/search-property?${params}`,
      {
        headers: {
          "x-rapidapi-key": process.env.RAPIDAPI_KEY!,
          "x-rapidapi-host": "bayut14.p.rapidapi.com",
        },
      }
    );

    if (!response.ok) {
      return NextResponse.json(
        { error: "Failed to fetch properties" },
        { status: response.status }
      );
    }

    const data = await response.json();
    return NextResponse.json(data);
  } catch {
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 }
    );
  }
}

Advanced Example: ISR for Property Detail Pages

Use Incremental Static Regeneration to pre-render property pages at build time and refresh them periodically:

// src/app/properties/[id]/page.tsx
import { searchProperties } from "@/lib/bayut-api";
import { notFound } from "next/navigation";

// Revalidate every hour
export const revalidate = 3600;

// Generate static params for the most popular areas
export async function generateStaticParams() {
  const popularAreas = ["5001", "5002", "5003"];
  const params: { id: string }[] = [];

  for (const areaId of popularAreas) {
    const data = await searchProperties({
      purpose: "for-sale",
      location_ids: areaId,
      page: "1",
    });

    for (const property of data.data.properties.slice(0, 5)) {
      params.push({ id: String(property.id) });
    }
  }

  return params;
}

export default async function PropertyPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  // Fetch property details (using search as a lookup)
  const data = await searchProperties({
    purpose: "for-sale",
    page: "1",
  });

  const property = data.data.properties.find(
    (p: { id: number }) => String(p.id) === id
  );

  if (!property) {
    notFound();
  }

  return (
    <main className="max-w-4xl mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold">{property.title.en}</h1>
      <p className="text-4xl font-bold mt-4">
        AED {property.price.toLocaleString()}
      </p>
      <div className="grid grid-cols-3 gap-4 mt-6 text-center">
        <div className="border rounded-lg p-4">
          <p className="text-2xl font-bold">{property.bedrooms}</p>
          <p className="text-gray-400">Bedrooms</p>
        </div>
        <div className="border rounded-lg p-4">
          <p className="text-2xl font-bold">{property.bathrooms}</p>
          <p className="text-gray-400">Bathrooms</p>
        </div>
        <div className="border rounded-lg p-4">
          <p className="text-2xl font-bold">{property.area}</p>
          <p className="text-gray-400">Sqft</p>
        </div>
      </div>
      <p className="mt-6 text-gray-400">{property.location}</p>
    </main>
  );
}

Next Steps

  • Read the full API documentation for all query parameters and response fields
  • Explore the Property Listing Websites use case for architecture patterns
  • Add loading states with Next.js loading.tsx and Suspense boundaries
  • Implement search with the autocomplete endpoint using a client component
  • Consider using SWR or React Query for client-side data fetching

Start Building with BayutAPI

Get your free API key and make your first request in under 5 minutes.

900 free requests/month — no credit card required