Recipes
Common page compositions that combine SDK fetchers and blocks into full pages. Copy, adapt, ship.
Listing Search Page
A full-page listings search with search box, grid, and filters. Uses the listing-grid and search-box blocks.
app/imoveis/page.tsx
import { ListingGrid } from "@/components/garagem/listing-grid";
import { SearchBox } from "@/components/garagem/search-box";
export default function ListingsPage({
searchParams,
}: {
searchParams: Promise<{ city?: string; type?: string }>;
}) {
const params = await searchParams;
return (
<main className="mx-auto max-w-7xl px-4 py-8">
<h1 className="text-2xl font-bold mb-6">Imóveis</h1>
<div className="mb-8">
<SearchBox
siteId={process.env.GARAGEM_SITE_ID!}
onSelect={(listing) => {
window.location.href = `/imoveis/${listing.id}`;
}}
/>
</div>
<ListingGrid
siteId={process.env.GARAGEM_SITE_ID!}
filters={{
city: params.city,
propertyType: params.type,
}}
limit={24}
next={{ revalidate: 300 }}
/>
</main>
);
}Listing Detail Page
Individual listing page with gallery, details, and contact form. Uses gallery-carousel and contact-form blocks.
app/imoveis/[id]/page.tsx
import { getListing } from "@garagem-ai/site-sdk";
import { GalleryCarousel } from "@/components/garagem/gallery-carousel";
import { ContactForm } from "@/components/garagem/contact-form";
export default async function ListingDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const listing = await getListing({
siteId: process.env.GARAGEM_SITE_ID!,
listingId: id,
next: { revalidate: 60 },
});
const price = listing.price?.toLocaleString("pt-BR", {
style: "currency",
currency: "BRL",
});
return (
<main className="mx-auto max-w-5xl px-4 py-8">
<GalleryCarousel media={listing.media} />
<div className="mt-8 grid gap-8 lg:grid-cols-3">
<div className="lg:col-span-2 space-y-4">
<h1 className="text-2xl font-bold">{listing.title}</h1>
<p className="text-xl font-semibold">{price ?? "Sob consulta"}</p>
<p className="text-muted-foreground">{listing.description}</p>
<div className="flex flex-wrap gap-2 mt-4">
{listing.amenities.map((a) => (
<span key={a} className="rounded-full bg-muted px-3 py-1 text-xs">
{a}
</span>
))}
</div>
</div>
<aside>
<ContactForm
siteId={process.env.GARAGEM_SITE_ID!}
listingId={listing.id}
title="Tenho interesse"
subtitle="Preencha e um corretor entrará em contato."
metaPixelEvent="Lead"
/>
</aside>
</div>
</main>
);
}Homepage with Featured Listings
Landing page showing featured listings and a CTA. Minimal setup.
app/page.tsx
import { getListings } from "@garagem-ai/site-sdk";
import { ListingCard } from "@/components/garagem/listing-card";
import { ContactForm } from "@/components/garagem/contact-form";
export default async function HomePage() {
const { listings } = await getListings({
siteId: process.env.GARAGEM_SITE_ID!,
filters: { limit: 6 },
next: { revalidate: 300 },
});
return (
<main className="mx-auto max-w-7xl px-4 py-12">
<section className="text-center mb-12">
<h1 className="text-4xl font-bold">Encontre seu imóvel</h1>
<p className="mt-2 text-lg text-muted-foreground">
Os melhores imóveis da região, tudo em um lugar.
</p>
</section>
<section className="mb-16">
<h2 className="text-xl font-semibold mb-6">Destaques</h2>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{listings.map((listing) => (
<ListingCard
key={listing.id}
listing={listing}
href={`/imoveis/${listing.id}`}
/>
))}
</div>
</section>
<section className="mx-auto max-w-md">
<ContactForm
siteId={process.env.GARAGEM_SITE_ID!}
title="Fale conosco"
subtitle="Não encontrou o que procura? Conte pra gente."
/>
</section>
</main>
);
}