← Blog

Under the hood: building vincentaugerot.com with Astro

How I designed a bilingual personal site that doubles as a working demo: static-first, no client-side framework, accessible, and soon with an AI conductor that answers from my own content.

Cloud3 minEN · FR

The route

A résumé lists stations, but it never shows the journey. I wanted a site where a recruiter or a client could see in ten seconds how my skills connect, then dig into any stop. Hence the transit-map metaphor: lines are skills, stations are jobs, interchanges are where things got interesting.

Five lines run through the whole site: Backend .NET, Leadership, Cloud, Entrepreneur and AI. Every experience and every project declares its own, and that single piece of data draws the map, colours the badges and links the pages together.

Astro, Markdown and no client-side framework

Astro renders static HTML and ships zero JavaScript by default. I took that all the way: the site uses no client-side framework, so there are no islands to hydrate. The few interactive behaviours (the map filter, the split-flap boards, the language pick) are small TypeScript scripts, mostly custom elements, which Astro bundles and minifies at build time.

Articles, experiences and projects are Markdown files in typed content collections. The frontmatter is validated with Zod at build time: an end date before the start date, an unknown line or a station off the map fails the build instead of breaking a page in production.

export const experienceSchema = z
  .object({
    company: z.string(),
    start: z.coerce.date(),
    end: z.coerce.date().optional(),
    lines: z.array(lineSchema).min(1),
    mapStation: mapStationSchema,
    // ...
  })
  .refine((data) => !data.end || data.end >= data.start, {
    message: 'end must be after start',
    path: ['end'],
  });

The schemas have their own Vitest tests. Content is the main data of this site, and it deserves the same care as the code.

The network map

The map is not an image. It is an SVG computed at build time from the frontmatter. Each experience carries a position on the timeline and the list of its lines. A station on a single line becomes a circle. On several lines, it becomes an interchange, drawn as a tall pill crossing the tracks.

mapStation:
  x: 540 # 60 + (year - 2010) × 60
  lanes: [backend, lead]

Adding a job means writing one Markdown file. The map, the timeline and the links update themselves.

For interaction, I followed progressive enhancement. Without JavaScript, every station is a plain link to its page, and every summary is listed below the map. With JavaScript, clicking a station filters the summaries down to its own, and clicking it again shows them all. For readers who prefer text to a diagram, a list view replaces the map in one click, and the choice is remembered.

The departures board

The split-flap boards on the home and projects pages are server-rendered text. The script only cycles through random characters before landing on the right one, cell by cell, with a slight offset to mimic the mechanism.

The animation respects prefers-reduced-motion: in that case, the text appears right away. On the home page, the board that rotates through my different hats also has a pause button, because automatic rotation is still motion, even when slow. And every facet stays listed for screen readers, which never have to wait for the animation.

Bilingual by design

I work in French and English every day, so the site does too. Crossing the bridge in the header switches language and takes you to the matching page, not back to the home page. Under the hood, it is Astro’s native i18n routing, with hreflang tags on every page and an x-default for search engines.

// astro.config.mjs
export default defineConfig({
  i18n: {
    locales: ['en', 'fr'],
    defaultLocale: 'en',
    routing: {
      prefixDefaultLocale: true,
      // The root page handles language detection itself.
      redirectToDefaultLocale: false,
    },
  },
});

The site root does not blindly redirect to English. It reads the browser’s preferred languages and sends you to the first one the site offers: a browser set to fr-BE lands on the French version. Without JavaScript, a fallback redirect and two visible links take over.

Accessibility

Transit signage is built for legibility, and so is this site. A few concrete choices:

  • Colours in two versions. Line colours are designed for the tracks, and some are too light for text. Each line therefore has a dedicated text colour, darkened where needed, that reaches at least 4.5:1 contrast (WCAG 2.1 AA).
  • Code too. The github-dark syntax theme rendered comments below the AA threshold. I switched to github-dark-default, which meets it.
  • Keyboard everywhere. Every station on the map is focusable. Since SVG links do not take an outline reliably, the focus ring is drawn on the station’s shape itself.
  • Motion under control. Reduced motion is respected everywhere, including the smooth scroll to the map summaries.

Hosting

The site is fully static and hosted on Cloudflare. No server to maintain, no database: a build produces HTML, CSS and a few scripts, served close to the visitor.

Next stop: a RAG assistant

The next stop is a conductor: an assistant that will answer questions about my career, only from the content of this site and with links back to the sources. The Markdown, already structured and validated, is its natural foundation. It will get its own article once it is built.

Terminus. Thanks for riding along.