← Files WixARCHIVED FILE

skills/wix-headless-templates/rentals/project/src/pages/rentals/[slug].astro

3.09 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

---
// Rental detail + booking page.
//
// This is a Wix ITEM PAGE: the wixMetadata export + <SEO.Tags> below let the site owner edit
// this page's title/description/OG in the dashboard and register the route in the sitemap. A
// rental is a Bookings service, so the Bookings service-page metadata applies. Keep all three SEO
// pieces and this frontmatter; swap the island import to YOUR rental component. The island is
// client:only — availability is time-zone/session-specific.
import SiteLayout from "../../layouts/SiteLayout.astro";
import RentalBookingView from "../../components/rentals/RentalBookingView";
import { fetchRentalBySlug } from "../../wix/rentals/rentals";
import { WIX_APPS } from "@wix/essentials";
import { SEO } from "@wix/seo/components";
import { loadSEOTagsServiceConfig } from "@wix/seo/services";
import { seoTags } from "@wix/seo";

export const wixMetadata = {
  appDefId: WIX_APPS.bookings.id,
  pageIdentifier: WIX_APPS.bookings.servicePageMetadata.pageIdentifier,
  identifiers: {
    slug: WIX_APPS.bookings.servicePageMetadata.identifiers.slug,
  },
};

const slug = Astro.params.slug!;

// Behind Wix's proxy the request URL is internal — the real public page URL arrives on
// x-wix-forwarded-url; the SEO service needs the public one.
const forwardedUrl = Astro.request.headers.get("x-wix-forwarded-url");
const pageUrl =
  forwardedUrl && URL.canParse(forwardedUrl) && /^https?:$/.test(new URL(forwardedUrl).protocol)
    ? forwardedUrl
    : Astro.url.href;

let rental = null;
let seoTagsServiceConfig = null;
try {
  [rental, seoTagsServiceConfig] = await Promise.all([
    fetchRentalBySlug(slug),
    loadSEOTagsServiceConfig({
      pageUrl,
      itemType: seoTags.ItemType.BOOKINGS_SERVICE,
      itemData: { slug },
    }).catch(() => null),
  ]);
} catch {
  // Guarded: an unhandled SSR throw truncates the response mid-stream.
}

if (!rental) {
  return new Response(null, { status: 404 });
}
---

<SiteLayout title={rental.name}>
  <SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" />

  <div class="grid gap-10 md:grid-cols-2">
    <div>
      {rental.imageUrl && (
        <div class="aspect-[4/3] overflow-hidden rounded-xl bg-secondary">
          <img src={rental.imageUrl} alt={rental.name} class="h-full w-full object-cover" />
        </div>
      )}
      <h1 class="mt-5 text-2xl font-semibold tracking-tight">{rental.name}</h1>
      {rental.tagLine && <p class="mt-1 text-muted-foreground">{rental.tagLine}</p>}
      <p class="mt-2 text-sm text-muted-foreground">
        {rental.rateLabel && <span class="text-foreground">{rental.rateLabel}</span>}
        {rental.rateLabel && rental.rangeLabel ? " · " : ""}
        {rental.rangeLabel}
      </p>
      {rental.locations.some((l) => l.name) && (
        <p class="mt-1 text-sm text-muted-foreground">{rental.locations.map((l) => l.name).filter(Boolean).join(" · ")}</p>
      )}
      {rental.description && <p class="mt-4 text-sm leading-relaxed text-muted-foreground">{rental.description}</p>}
    </div>
    <div>
      <RentalBookingView client:only="react" rental={rental} />
    </div>
  </div>
</SiteLayout>

SHA-256: 7af12fbf1627ab97bac19a94785115fa812dd21bf0bd46456717799b1530d1ef