Use this file to discover all available pages before exploring further.
The Real Estate Template handles the full SEO stack automatically: every page emits its own <title>, description, Open Graph, and Twitter Card meta tags through the usePageSeo() composable, while JSON-LD structured data is injected as <script type="application/ld+json"> blocks via useJsonLd(). A dynamic Nitro server route generates /sitemap.xml at runtime (or pre-renders it during pnpm generate) and a companion route serves /robots.txt. All absolute-URL features — canonical links, og:url, OG image URLs, sitemap entries, and the robots sitemap reference — are gated on a single environment variable: NUXT_PUBLIC_SITE_URL.
Set this variable to your production domain before building. It is consumed by nuxt.config.ts and stored in runtimeConfig.public.siteUrl, making it available to both the client bundle and the Nitro server.
NUXT_PUBLIC_SITE_URL=https://www.your-agency.com
The value is normalized the same way in every consumer: any trailing slash is stripped so that concatenating it with a path that starts with / never produces //.
When NUXT_PUBLIC_SITE_URL is empty (the default), the sitemap returns HTTP 503, robots.txt emits Disallow: / blocking all crawlers, and canonical/og:url tags are omitted from every page. Never deploy to production without setting this variable.
usePageSeo() lives in app/core/composables/usePageSeo.ts. Although ~/core/composables is listed in nuxt.config.ts → imports.dirs (so it is technically available via auto-import), every page imports it explicitly by convention — this keeps call sites easy to audit and prevents the composable from leaking silently into component scope.
import { usePageSeo } from '~/core/composables/usePageSeo'
The composable returns computed values that each page passes to useSeoMeta() and useHead(). Each page is responsible for its own useSeoMeta call so that page-specific options (like ogType: 'article' on a property detail page) remain readable at the call site.
i18n keys (e.g. t('seo.home.title')) are resolved in the page and passed as plain strings to useSeoMeta. The usePageSeo() composable itself is i18n-agnostic — it only handles URL normalization, image resolution, and og:site_name.
Page title passes through unchanged; add a suffix here if desired (e.g. '%s — Agency Name')
ogImage
'/images/logo.svg'
Fallback OG image when no page-specific image or agency logo is resolved
twitterCard
'summary_large_image'
Twitter card type applied to every page
Per-page SEO copy (titles, descriptions) comes from i18n keys in the seo.* group in i18n/locales/en.json and i18n/locales/es.json. Both files must stay in sync — a key present in only one file causes the other locale to fall back to English silently.
Structured data is emitted via the useJsonLd() composable (app/core/composables/useJsonLd.ts), which wraps useHead to inject a <script type="application/ld+json"> block. It accepts a plain object, a ref, or a getter so callers can compose the payload from reactive sources.
No configuration is needed to enable or disable JSON-LD — it is wired directly into each page’s setup function and produces the same output in SSG, SSR, and Nitro server modes. NUXT_PUBLIC_SITE_URL is required for absolute @id and url fields; when the variable is empty those fields are omitted.
One ListItem per visible property card; position mirrors card order
/properties/[slug]
RealEstateListing + ItemList
RealEstateListing includes floorSize as a QuantitativeValue with an explicit unitCode; the second ItemList covers related properties (always emitted, itemListElement: [] when no related properties exist)
/agents
ItemList of ListItem
Each ListItem.item is a Person; worksFor on each Person references the agency home page @id
/contact
ContactPage
mainEntity: RealEstateAgent sharing the home page @id
/about
AboutPage
mainEntity: RealEstateAgent sharing the home page @id
The home page, contact page, and about page all share the same RealEstateAgent.@id (the home page’s absolute URL). This tells search engines the agency is a single knowledge-graph node regardless of which page they crawl first.
The related-properties ItemList on /properties/[slug] is always registered — it is not gated on related.length. When there are no related properties the itemListElement is an empty array, which is a valid and inert ItemList. The visible related-properties UI section is separately gated with v-if="related.length", so the user never sees an empty section while the structured data is still present for parsers.
The sitemap is a Nitro server route at server/routes/sitemap.xml.ts. It is both served dynamically at runtime and pre-rendered to disk during pnpm generate (listed in nuxt.config.ts → nitro.prerender.routes).
The sitemap reads siteConfig.agency.modules and includes routes only for enabled modules:
/ always included/about always included/contact when modules.contact === true/agents when modules.agents === true/properties when modules.properties === true/properties/* one URL per property from propertiesService.getAll()/developments when modules.developments === true
propertiesService.getAll() already filters out records with status: 'hidden'. Properties with any other status (available, sold, reserved, rented) are treated as public listings and appear in the sitemap. Per-development detail URLs (/developments/[slug]) are intentionally omitted — the detail route does not exist yet and emitting those URLs would point crawlers to 404s.
The output is a minimal urlset (no <lastmod>, <changefreq>, or <priority> elements) because the data models do not carry a last-modified timestamp. All three fields are optional per the sitemaps.org spec.
The robots file is a Nitro server route at server/routes/robots.txt.ts. Unlike the sitemap, it always returns HTTP 200 — returning 503 for robots.txt can confuse crawlers that probe it early.
User-Agent: *Disallow: /# Configure NUXT_PUBLIC_SITE_URL to enable crawling.
Both /sitemap.xml and /robots.txt are included in nitro.prerender.routes with failOnError: false, so a pnpm generate run without NUXT_PUBLIC_SITE_URL still succeeds — it writes the degraded versions to disk rather than failing the build.