Skip to content
tak.tn
← Back to blog
4 min read

Rebuilding my 2016 Drupal site with Astro 7: field notes

#astro#seo#drupal

For over a decade, my personal site ran on Drupal — because of course it did. I build Drupal platforms for a living. But the site had become the cobbler’s barefoot child: a single-page CV on an ageing Drupal 9, with 58 render-blocking stylesheets, no structured data, no sitemap, and content frozen somewhere around 2019.

I rebuilt it with Astro 7 — and this post documents the SEO-first architecture, because it is the same recipe I now recommend for any content site that does not need a CMS-driven front end.

Why static, and why Astro

My old Drupal site served exactly one page to anonymous visitors. Running a full CMS stack — PHP, database, security updates, cache layers — to serve what is effectively a static document is engineering debt with no return.

Astro’s value proposition fits this case perfectly:

  • Zero JavaScript by default. Every byte of JS must justify itself. My content pages ship none.
  • Content collections with typed frontmatter — the blog is markdown files in git, validated at build time.
  • First-class image and font tooling — more on that below.
  • The output is plain HTML on a CDN. There is nothing to hack and nothing to patch on a Tuesday night.

Note for my Drupal colleagues: this is not “Drupal is dead” talk. Editorial platforms with dozens of contributors need a real back office — that is what Drupal is for. Your personal site does not.

The parts that matter for SEO

Fonts without layout shift

Astro’s built-in Fonts API downloads the fonts at build time, self-hosts them with unicode-range subsets, and — the killer feature — generates size-adjusted fallback fonts automatically. The fallback Arial is metrically compatible with the real font, so the swap causes near-zero cumulative layout shift. This used to require a manual pipeline; it is now one config block.

One JSON-LD graph per page

Instead of scattering disconnected schema snippets, every page emits a single @graph linking entities by @id: WebSitePerson (my entity, with sameAs links to drupal.org, GitHub and LinkedIn) → the page node (WebPage, ProfilePage, BlogPosting…) → BreadcrumbList. Search engines — and increasingly LLMs — get a coherent identity graph instead of confetti.

For a personal brand, the Person entity is the whole game: it is what lets Google (and AI assistants) connect your site, your open-source work and your profiles into one “who is this” answer.

hreflang only where translations exist

The site is bilingual (English first, French under /fr/). A classic multilingual mistake is generating hreflang pairs for every URL by prefix substitution — including pages that were never translated. My hreflang links are generated from an explicit route map, so a page only claims an alternate that actually exists. Partial translation is fine; lying to crawlers is not.

Open Graph images from the build

Every page and post gets a branded 1200×630 OG image, rendered at build time with satori (HTML-ish template → SVG) and resvg (SVG → PNG). No runtime, no third-party service — just static PNGs on the CDN, in the brand’s navy and azure.

The boring plumbing, done properly

  • robots.txt generated from config, pointing at the sitemap index.
  • Sitemap with real lastmod values.
  • 301 redirects for every URL of the old Drupal site (/node/1, I will not miss you).
  • Canonicals, OG/Twitter tags and meta descriptions on every page — enforced by a single <Seo> component, so a page cannot be created without them.

Deployment: Cloudflare

The site deploys as static assets on Cloudflare Workers, with one server-rendered route: the contact form endpoint (Turnstile for bots, Resend for delivery — about fifty lines of code). Since Cloudflare acquired the Astro team in January 2026, this is the most invested-in deployment path in the ecosystem, and the free tier covers a personal site entirely.

The result

Lighthouse does not go higher than this, and the page weight went from megabytes of CSS/JS soup to a few dozen kilobytes. But the honest metric is different: the old site had one indexable page; this one has a content architecture designed to grow — services, case studies, and this blog.

The next posts will go deeper into individual pieces: the JSON-LD graph design, Drupal-to-static migration patterns, and Core Web Vitals debugging on Drupal platforms. À suivre.

A Drupal project or a technical SEO challenge?

Let's talk. Reply within one business day, no strings attached.

Get in touch