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.
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
IntersectionObserverscript (respectsprefers-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 default —
index.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]/usegetStaticPathsto generate the/es,/en, and/frvariants at build time. - Blog —
blog/index.astroandblog/[slug].astroare Catalan-only. Non-Catalan blog URLs 301-redirect to the Catalan equivalents inastro.config.mjs. - Seasonal pages —
summer-lab.astrois always built and served; the home-page promotional card that links to it is conditionally rendered based onisSummerLabVisible()fromsrc/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-h1through--font-size-h4,--font-size-p1through--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.
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 tohttps://borrissol.comfor correct sitemap and canonical URL generation.adapter—@astrojs/netlifyfor hybrid rendering (static pages + Netlify functions for the Keystatic panel).i18n—defaultLocale: '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 commandnpm run build, publish directorydist.[build.environment]— pinsNODE_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.