Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/constanza101/borrissol/llms.txt

Use this file to discover all available pages before exploring further.

Borrissol’s project-wide settings are spread across three files that each own a clearly defined domain: astro.config.mjs controls the framework, adapters, integrations, routing and redirects; src/config/site.ts centralises every SEO token and business detail that feeds structured data; and src/config.ts holds the WhatsApp deep-link utility consumed by all four language variants of the site. Keeping these three files in sync is the first step before going live or after any rebrand.

astro.config.mjs

The root framework config. It declares the deployment target, trailing-slash policy, 301 redirects, all integrations, and the i18n routing strategy.
astro.config.mjs
// @ts-check
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import netlify from '@astrojs/netlify';
import keystatic from '@keystatic/astro';
import react from '@astrojs/react';
import markdoc from '@astrojs/markdoc';

export default defineConfig({
  site: 'https://borrissol.com',
  adapter: netlify(),
  trailingSlash: 'never',
  i18n: {
    defaultLocale: 'ca',
    locales: ['ca', 'es', 'en', 'fr'],
    routing: { prefixDefaultLocale: false },
  },
  // ... redirects, integrations (see sections below)
});

site

site: 'https://borrissol.com'
The canonical origin. Astro uses this to generate absolute URLs in the sitemap, canonical tags, and Open Graph meta. Update this value if the domain ever changes.

adapter

adapter: netlify()
Provided by @astrojs/netlify (^7.0.12). All pages are statically prerendered except the Keystatic admin routes, which require SSR. The adapter wraps those in Netlify Functions automatically with no extra configuration.

trailingSlash

trailingSlash: 'never'
Ensures all URLs are canonical without a trailing slash (e.g. /es/tallers not /es/tallers/). The Netlify adapter enforces this at the edge.

redirects

All redirects are declarative 301s in the Astro config. They fall into two groups: Legacy /ca/*/* — Catalan is the default locale served at root (no /ca/ prefix). Any legacy URLs with the /ca prefix get permanently redirected to strip it. /<lang>/blog/blog — The blog is intentionally Catalan-only. Non-Catalan blog URLs redirect to the Catalan blog so shared links and old bookmarks still resolve.
redirects: {
  // /ca/* → / (legacy prefix removal)
  '/ca':             { status: 301, destination: '/' },
  '/ca/gallery':     { status: 301, destination: '/gallery' },
  '/ca/press':       { status: 301, destination: '/press' },
  '/ca/blog':        { status: 301, destination: '/blog' },
  '/ca/blog/[slug]': { status: 301, destination: '/blog/[slug]' },

  // /<lang>/blog → /blog (blog is CA-only)
  '/es/blog':        { status: 301, destination: '/blog' },
  '/es/blog/[slug]': { status: 301, destination: '/blog/[slug]' },
  '/en/blog':        { status: 301, destination: '/blog' },
  '/en/blog/[slug]': { status: 301, destination: '/blog/[slug]' },
  '/fr/blog':        { status: 301, destination: '/blog' },
  '/fr/blog/[slug]': { status: 301, destination: '/blog/[slug]' },
},
Astro requires redirect destinations to match real routes. Wildcards like /ca/* do not work in Astro’s redirects object — each sub-route must be listed explicitly.

integrations

integrations: [
  sitemap({
    // Emit hreflang cross-references between language variants of each page.
    // Keys must match the locale codes in `i18n.locales`.
    i18n: {
      defaultLocale: 'ca',
      locales: {
        ca: 'ca-ES',
        es: 'es-ES',
        en: 'en-US',
        fr: 'fr-FR',
      },
    },
    // Defensive filter: exclude any stray /ca routes from the sitemap.
    filter: (page) => !page.includes('/ca'),
  }),
  react(),
  markdoc(),
  keystatic(),
],
IntegrationPackagePurpose
sitemap()@astrojs/sitemap ^3.7.3Generates sitemap.xml with hreflang rel="alternate" entries for all four locales. Mirrors the hreflang tags already in each page’s <head>.
react()@astrojs/react ^5.0.7Required by Keystatic’s admin UI, which is a React application. No React is used on public-facing pages.
markdoc()@astrojs/markdoc ^1.0.6Renders blog post bodies written in Markdoc (a superset of Markdown).
keystatic()@keystatic/astro ^5.0.6Mounts the Keystatic admin at /keystatic and handles the Git-backed CMS API routes.

i18n

i18n: {
  defaultLocale: 'ca',
  locales: ['ca', 'es', 'en', 'fr'],
  routing: {
    prefixDefaultLocale: false,
  },
},
KeyValueMeaning
defaultLocale'ca'Catalan content is served at root (e.g. /tallers).
locales['ca', 'es', 'en', 'fr']The four supported languages.
prefixDefaultLocalefalseCatalan URLs have no language prefix; Spanish, English and French are prefixed (/es/, /en/, /fr/).

src/config/site.ts

This file exports the SITE constant — a single object that every SEO and structured-data component reads from. It is the single source of truth for the business name, address, contact details, hours, and social links.

Type definitions

type SchemaBusinessType = 'ProfessionalService' | 'LocalBusiness' | 'ArtGallery';

interface PostalAddress {
  streetAddress?: string;
  addressLocality?: string;
  addressRegion?: string;
  postalCode?: string;
  addressCountry: string;
}

interface GeoCoordinates {
  latitude: number;
  longitude: number;
}

interface BusinessConfig {
  type: SchemaBusinessType;
  name: string;
  description: string;
  email?: string;
  telephone?: string;
  address: PostalAddress;
  geo?: GeoCoordinates;
  priceRange?: string;
  openingHours?: string[];
  sameAs?: string[];
}

interface SiteConfig {
  name: string;
  url: string;
  locale: string;
  logo: string;
  defaultOgImage: string;
  business: BusinessConfig;
}

The SITE export

src/config/site.ts
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',
    ],
  },
};

Key fields

FieldValueUsed by
urlhttps://borrissol.comCanonical URLs, Open Graph, JSON-LD
localees_ESOpen Graph og:locale
logo/images/borrissol-logo-b-512.pngJSON-LD Organization.logo (512×512px per Google spec)
defaultOgImage/og-default.jpgFallback og:image for pages without a cover photo
business.typeLocalBusinessJSON-LD @type — controls which Google rich results are eligible
business.geo41.5373926, 2.4466578JSON-LD geo node — places the business in Google’s local map pack
business.openingHours['Mo-Sa 09:30-13:30', 'Mo-Sa 16:30-20:30']JSON-LD openingHours — shown in Knowledge Panel
business.sameAsInstagram, TikTok URLsJSON-LD sameAs — reinforces entity disambiguation
Update SITE before going live after any rebrand or change of address. The object feeds Seo.astro, all JSON-LD structured data blocks, and the Open Graph tags on every page — stale values will be indexed by search engines.

src/config.ts

A minimal module that exposes the WhatsApp contact number and the waHref URL builder.
src/config.ts
const WHATSAPP_BASE = 'https://wa.me/34673247520';

export const WHATSAPP_HREF = WHATSAPP_BASE;

export const waHref = (text: string) =>
  `${WHATSAPP_BASE}?text=${encodeURIComponent(text)}`;

waHref(text)

Builds a WhatsApp deep link with a pre-filled message. Every CTA button and the floating WhatsApp FAB call waHref() with a locale-specific message string fetched from src/i18n/ui.ts. The resulting URL opens the WhatsApp app (or web) with the message pre-composed, ready to send. Example:
import { waHref } from '../../config';

const href = waHref('Hola! Vull apuntar-me a un taller de tufting.');
// → 'https://wa.me/34673247520?text=Hola%21%20Vull%20apuntar-me%20a%20un%20taller%20de%20tufting.'

Environment variables

No .env file is required. All project configuration is in code. Keystatic Cloud credentials (used for authenticating the /keystatic admin in production) are configured through the Keystatic Cloud dashboard and injected by Netlify at build/runtime — they are never stored locally.

Node version

The project requires Node ≥ 22.12.0, declared in package.json and pinned in netlify.toml:
netlify.toml
[build.environment]
  NODE_VERSION = "22.12.0"
Use nvm or fnm locally to match this version.

.npmrc

.npmrc
legacy-peer-deps=true
legacy-peer-deps=true is required to resolve a peer dependency conflict between @keystatic/astro 5.0.6 and Astro 6. Without it, npm install exits with an ERESOLVE error. Do not remove this flag until the upstream conflict is resolved in a future Keystatic release.

Build docs developers (and LLMs) love