← Files WixARCHIVED FILE

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

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

↓ Download file

See the change to this file →

---
// Service 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.
// Keep all three SEO pieces and this frontmatter; swap the island import to YOUR booking
// component. The booking island is client:only — availability is timezone/session-specific.
import SiteLayout from "../../layouts/SiteLayout.astro";
import ServiceBookingView from "../../components/bookings/ServiceBookingView";
import { fetchServiceBySlug } from "../../wix/bookings/services";
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 service = null;
let seoTagsServiceConfig = null;
try {
  [service, seoTagsServiceConfig] = await Promise.all([
    fetchServiceBySlug(slug),
    loadSEOTagsServiceConfig({
      pageUrl,
      itemType: seoTags.ItemType.BOOKINGS_SERVICE,
      itemData: { slug },
    }).catch(() => null),
  ]);
} catch {
  // Guarded: an unhandled SSR throw truncates the response mid-stream.
}

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

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

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

SHA-256: 82967a5bb409e0d899ec44a79cda39828c3398fdbd8d4bbc4e70564107603e9a