> ## Documentation Index
> Fetch the complete documentation index at: https://constanza101-borrissol-28.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SEO, structured data, and Open Graph metadata in Borrissol

> How Borrissol achieves Lighthouse SEO 100: JSON-LD schemas, Open Graph, hreflang, sitemap config, canonical URLs, and the central site.ts config.

All SEO metadata in Borrissol flows through two files: `src/components/Seo.astro`, which is included in the `<head>` of every page, and `src/config/site.ts`, which holds every business detail and URL in one central place. This architecture means there is a single canonical source for the business name, telephone number, postal address, opening hours, and social links — changing a value in `site.ts` propagates to every JSON-LD schema, every Open Graph tag, and every page automatically. The result is a consistent Lighthouse SEO score of **100** across all pages.

## Central site config (`src/config/site.ts`)

The `SITE` export is the authoritative record for all business metadata:

```typescript src/config/site.ts theme={null}
export const SITE: SiteConfig = {
  name: 'Borrissol',
  url: 'https://borrissol.com',
  locale: 'es_ES',
  logo: '/images/borrissol-logo-b-512.png',
  defaultOgImage: '/og-default.jpg',

  business: {
    type: 'LocalBusiness',
    name: 'Borrissol Espai Creatiu',
    description:
      'Espai creatiu de tallers tèxtils a Mataró. Especialistes en tufting, bordats i experiències artístiques.',
    email: 'borrissolespaicreatiu@gmail.com',
    telephone: '+34673247520',
    address: {
      streetAddress: 'Carrer de Sant Antoni, 17, baix',
      addressLocality: 'Mataró',
      addressRegion: 'Catalunya',
      postalCode: '08301',
      addressCountry: 'ES',
    },
    geo: {
      latitude: 41.5373926,
      longitude: 2.4466578,
    },
    priceRange: '€€',
    openingHours: ['Mo-Sa 09:30-13:30', 'Mo-Sa 16:30-20:30'],
    sameAs: [
      'https://www.instagram.com/borrissol_espai_creatiu',
      'https://www.tiktok.com/@borrissol_espai_creatiu',
    ],
  },
};
```

The `geo` coordinates place the business in Google's local map pack for location-intent searches ("tufting workshop near me"). The `sameAs` array links the entity to its verified social profiles.

## JSON-LD structured data

`Seo.astro` emits several JSON-LD schemas on every relevant page. All data is drawn from `SITE` — nothing is hardcoded inside the component.

<CardGroup cols={2}>
  <Card title="LocalBusiness" icon="map-pin">
    Includes name, full postal address, geo coordinates, telephone, `openingHours`, `priceRange`, and `sameAs` links to Instagram and TikTok.
  </Card>

  <Card title="Organization" icon="building">
    Emitted alongside `LocalBusiness`. Includes the logo as an `ImageObject` node with explicit 512×512 pixel dimensions — required for Google rich results eligibility.
  </Card>

  <Card title="FAQPage" icon="circle-help">
    Generated dynamically from FAQ sections on landing pages. Each question–answer pair in `ui.ts` (e.g. `tb.faq.q1` / `tb.faq.a1`) is serialized into a `Question` entity.
  </Card>

  <Card title="CourseSchema" icon="graduation-cap">
    Applied to workshop landing pages (Team Building, Pelussetes, Borla, Summer Textile Lab). Includes an `AggregateOffer` covering the price range for the course format.
  </Card>
</CardGroup>

An `AggregateRating` node is also emitted globally: **5.0 out of 5 · 28 reviews**, sourced from the Google Maps reviews displayed in the testimonials section.

## Open Graph and Twitter Card

`Seo.astro` writes the full set of Open Graph and Twitter Card meta tags on every page:

* `og:title`, `og:description`, `og:url`, `og:image`, `og:image:width`, `og:image:height`
* `og:locale` (from `localeMap` in `utils.ts` — e.g. `ca_ES`, `es_ES`, `en_US`, `fr_FR`)
* `og:locale:alternate` tags for the other three locales
* `twitter:card` set to `summary_large_image`
* `twitter:title`, `twitter:description`, `twitter:image`

## Hreflang tags

Every page's `<head>` contains `<link rel="alternate" hreflang="...">` tags for all four locales plus `x-default`, generated by `Seo.astro` using `getAlternatePath` from `utils.ts`. The sitemap reinforces this signal:

```js astro.config.mjs theme={null}
sitemap({
  i18n: {
    defaultLocale: 'ca',
    locales: {
      ca: 'ca-ES',
      es: 'es-ES',
      en: 'en-US',
      fr: 'fr-FR',
    },
  },
  filter: (page) => !page.includes('/ca'),
}),
```

The `filter` keeps stray `/ca/*` paths from appearing in the sitemap (they were the old URL structure before the `[lang]` refactor and are now handled by 301 redirects).

## Canonical URLs

Each page sets its own canonical URL via a `<link rel="canonical">` tag in `Seo.astro`. The canonical always points to the current page's definitive URL (including the locale prefix for non-default locales), preventing duplicate-content signals between language variants.

## Per-page title and description

Each landing page passes its `titleKey` and `descKey` props to `LandingPage.astro`, which resolves them through `useTranslations` against the `ui.ts` catalog. This means page titles and meta descriptions are fully translated for all four locales without any additional per-page SEO work. For example:

| Key                   | CA                       | ES                        | EN                        |
| --------------------- | ------------------------ | ------------------------- | ------------------------- |
| `page.tb.title`       | `Team Building Creatiu…` | `Team Building Creativo…` | `Creative Team Building…` |
| `page.tb.description` | `Team building tèxtil…`  | `Team building textil…`   | `Textile team building…`  |

## Sitemap

The sitemap is generated by `@astrojs/sitemap` at build time. It includes all statically prerendered pages with their hreflang cross-references. A defensive filter strips any URL containing `/ca` to guard against stale routes re-emerging after refactors.

## `robots.txt`

The `robots.txt` file allows all crawlers on `/` and explicitly lists the sitemap URL. Common social and search bots (Googlebot, Twitterbot, facebookexternalhit, etc.) are explicitly permitted. WordPress/PHP probe paths like `/wp-admin/` and `/.env` are blocked at the Netlify edge layer via `netlify.toml` redirect rules before they ever reach Astro.

## OG image guidelines

<Warning>
  **Use JPG, not PNG, for OG images.** A raw PNG export from Figma can easily weigh 4 MB — silently burning bandwidth on every social share preview. The default OG image (`/og-default.jpg`) must be:

  * **Format:** JPG
  * **Dimensions:** 1200 × 630 px
  * **File size:** under 300 KB
</Warning>

Per-page OG images follow the same constraints. Always export from Figma as JPG with quality \~85, verify the file size with `ls -lh`, and commit only after confirming it is under 300 KB.

## Lighthouse scores

| Category       | Score |
| -------------- | ----- |
| Performance    | 99    |
| Accessibility  | 97    |
| Best Practices | 100   |
| SEO            | 100   |

<Tip>
  The SEO 100 score is maintained by keeping all metadata in `site.ts` and `ui.ts` — never scattering values across components. If you add a new page or schema type, add its config to `site.ts` first, then reference it from `Seo.astro`.
</Tip>
