Skip to main content
The Borrissol codebase follows the standard Astro project layout with a few deliberate structural choices: all business and SEO configuration is centralised in src/config/ (split across site.ts, reviews.ts, and seasonal.ts), all translated copy lives in src/i18n/ui.ts, all design tokens live in src/styles/theme.css, and per-language routing is handled by a single [lang]/ dynamic route rather than duplicated per-language directories. Configuration files at the root (astro.config.mjs, keystatic.config.ts, netlify.toml) govern the build, CMS schema, and deployment respectively.

Directory tree

Directory and file reference

src/assets/images/

All content images are stored here and run through the Astro asset pipeline at build time. Astro’s <Picture> component converts sources to AVIF + WebP with a responsive srcset, and automatically generates the correct width/height attributes to prevent Cumulative Layout Shift (CLS = 0). Source files should be no larger than approximately 2× the widest responsive variant that will be served.
The hero image uses loading="eager" and fetchpriority="high" to optimise LCP (1.8 s on mobile). All other images use loading="lazy".

src/components/

Components are organised into four informal categories: Seo.astro is the single place for all <meta> tags, JSON-LD structured data blocks (LocalBusiness, FAQPage, ImageObject, AggregateRating), Open Graph/Twitter Card tags, hreflang links, and canonical URL. It accepts per-page overrides as props and falls back to the values in src/config/site.ts.

src/config/

The config directory holds three separate modules — not one. Each is a single-purpose export: Update site.ts rather than touching individual components when business details change. Never hardcode review counts or aggregate ratings inline — always edit reviews.ts.

src/content/blog/

MDX files authored via the Keystatic CMS panel at /keystatic. Posts are processed through Astro Content Collections (getCollection('blog')). The blog is intentionally Catalan-only to keep the editorial workload sustainable — /es/blog, /en/blog, and /fr/blog all 301-redirect to /blog.

src/i18n/

src/layouts/

Layout.astro is the single shared shell for every page. It injects:
  • Google Analytics 4 (gtag.js) with Consent Mode v2 Advanced — GA loads on every visit in cookieless mode by default; cookies are only granted after the user accepts the cookie banner.
  • A skip-to-content link as the first child of <body> for keyboard accessibility.
  • Preconnect hints for third-party domains.
  • The scroll-reveal IntersectionObserver script (respects prefers-reduced-motion).
  • Self-hosted Roboto WOFF2 font declarations with font-display: swap.

src/pages/

Astro’s file-based router maps files to URLs directly. The routing strategy for Borrissol:
  • Catalan defaultindex.astro, gallery.astro, press.astro, and all workshop pages (tufting.astro, punch-needle.astro, felting.astro, loom.astro, borla.astro, summer-lab.astro, team-building.astro, pelussetes.astro) sit at the top level and are served at their respective root paths. There is no /ca/ prefix.
  • Other languages — mirrored files inside [lang]/ use getStaticPaths to generate the /es, /en, and /fr variants at build time.
  • Blogblog/index.astro and blog/[slug].astro are Catalan-only. Non-Catalan blog URLs 301-redirect to the Catalan equivalents in astro.config.mjs.
  • Seasonal pagessummer-lab.astro is always built and served; the home-page promotional card that links to it is conditionally rendered based on isSummerLabVisible() from src/config/seasonal.ts.

src/styles/theme.css

The single source of truth for the entire design system. Contains all CSS custom properties on :root:
  • Colors--color-white, --color-light, --color-mid, --color-muted, --color-black, plus semantic aliases (--bg, --fg, --fg-muted, --border-hairline).
  • Typography--font-size-h1 through --font-size-h4, --font-size-p1 through --font-size-p3, --font-size-ui, weight, line-height, and letter-spacing tokens.
  • Spacing--space-xs (8 px) through --space-xl (80 px).
  • Border radius--radius-pill, --radius-md, --radius-sm, --radius-xs.
  • Icons — stroke width, color, and size tokens for each context (inline, button, nav, feature, hero).
  • Utility classes.text-h1.text-h4, .text-p1.text-p3, .text-ui, .text-muted, .eyebrow, .card, .media, .btn, .btn-primary, .btn-secondary, .btn-tertiary, .btn-sm, .badge, .badge-light, .input, .checkbox.
Never hardcode a color, spacing value, font size, or border radius anywhere in the codebase. Always reference a token from theme.css. If a value isn’t tokenized yet, add it to theme.css first, then use it.

keystatic.config.ts

Defines the Keystatic CMS schema — the blog collection, its fields (title, publish date, description, body MDX), and the storage strategy (local Git-backed files in src/content/blog/). In production, Keystatic authenticates via Keystatic Cloud; serverless function invocations only occur when the editor uses the /keystatic UI — regular visitors load fully static pages at zero function cost.

astro.config.mjs

The Astro build configuration. Key settings:
  • site — set to https://borrissol.com for correct sitemap and canonical URL generation.
  • adapter@astrojs/netlify for hybrid rendering (static pages + Netlify functions for the Keystatic panel).
  • i18ndefaultLocale: 'ca', locales ['ca', 'es', 'en', 'fr'], prefixDefaultLocale: false (Catalan served at root without /ca/ prefix).
  • redirects — 301 rules for legacy /ca/* URLs and cross-language blog redirects (all /<lang>/blog/blog).
  • integrations@astrojs/sitemap (with hreflang cross-references between language variants), @astrojs/react (required by Keystatic), @astrojs/markdoc, @keystatic/astro.

netlify.toml

Controls the Netlify build and edge behaviour:
  • [build] — build command npm run build, publish directory dist.
  • [build.environment] — pins NODE_VERSION = "22.12.0" to match the Astro 6 requirement.
  • Bot-trap redirects — a series of forced-404 [[redirects]] rules that short-circuit common WordPress/PHP/admin-tool scanner probes (/wp-admin/*, /wp-login.php, /.env, /.git/*, etc.) at the Netlify edge layer. This prevents bot requests from invoking Astro SSR and consuming function credits.

URL routing reference