If everything went according to plan, no one should have noticed anything. But for a few days now, this site has been completely new.
We took our old Next 13 site and completely rewrote it in Astro! 🧑🚀🚀
Not only that: we also moved from the classic Vercel hosting to a completely server-side rendering approach on a VPS, which allows our team an editing experience with immediate feedback, combined with a blazing-fast CDN for the website visitors.
Astro was a bet. We didn't know it well enough to be sure everything would go as expected. But the result was a complete success from every perspective, and the development experience was extremely educational and — not something to be taken for granted — fun.
The final architecture offers performance and consumption typical of a static site, coupled with instant and granular page-level invalidation thanks to our Cache Tags. The dream of every website.
This is part one of a series of articles in which we'll try to summarize our journey, sharing many cool little Astro details we discovered (or totally came up with) in the process. We hope they can be useful to those, who like us, manage a content-driven site and would like to consider an alternative to the classic Next/Nuxt/Svelte + Vercel/Netlify stack.
Now let’s be clear – by no means are we suggesting we’re against Next.js. Heck, we’re incredibly proud of the official Next.js conference starter which was originally built with DatoCMS in 2020! However, considering our content and sitemap, several factors came into play to make this decision.
Why did you do it?
There are many reasons why we needed to make BIG changes to our website.
TypeScript
Our Next site was a project born in 2019: five years in the magical frontend world is a geological era (unfortunately). At the time, there were no reliable typed GraphQL schema generators. The result was that the old site was written in pure JS, without TypeScript. Over time, as pages increased, we felt less and less comfortable making big modifications with the confidence of not breaking anything.
Hence the first objective: regain confidence by switching to a complete TypeScript codebase, where every GraphQL query would be fully typed. But since that meant rewriting a good chunk of our code anyways, why not consider alternatives to Next.js?
Eating our own dog food
Our product, DatoCMS, has changed A LOT since 2019. Yet our Next site wasn't leveraging some of the coolest features we've released in recent times, like GraphQL "Strict Mode" — which pairs wonderfully with TypeScript — but especially Cache Tags, which can offer top-notch performance through completely static and cached content, while still letting visitors access the latest version of any page, seconds after changes have been published.
We wanted a site that represented the state of the art of what DatoCMS can offer.
A simpler mental model
In 2019, Next.js introduced getStaticProps()
along with Draft Mode — a revolutionary hybrid static/dynamic approach that was a breath of fresh air compared to our former Gatsby setup. The developer experience was delightful and straightforward.
Fast forward to today, and the React ecosystem has transformed dramatically. Setting up React is now so complex that React itself recommends using it exclusively through a framework. Next.js and React have become almost indistinguishable, to the point where, at the time of its public launch, Next.js 15 was using... an unreleased React release candidate?
Starting with Next.js 14, the introduction of the app router, Server Components, and Incremental Regeneration (ISR) have exponentially increased complexity. Our team frequently encountered bewildering results and errors, with Next.js documentation offering little clarity about the underlying mechanics. We're not alone in this frustration — many developers are questioning the framework's direction (i.e. Why I Won't Use Next.js).
While Next.js 15 addresses some issues, and is an incredibly powerful framework, it feels like React/Next is moving in a direction that isn't ours. For a content-driven site with static pages that look the same to every visitor, these increasingly sophisticated solutions feel like overkill and force us into unnecessary complexity.
Hence the last objectives:
return to a simple mental model that doesn't require a frontend degree to navigate without making massive errors.
design a simple, standards-based architecture that offers maximum control and remains cost-effective.
Why Astro?
With these premises, choosing Astro as the reference framework was quite an obvious consequence.
To use DatoCMS Cache Tags, the only requirement at the "engine" level of the website is to offer a server-side rendering mode capable of freely manipulating the headers of each page's response, and Astro checks the box. This is certainly not a strict requirement: Next, Nuxt, SvelteKit... are all frameworks that offer server-side rendering.
The critical distinction lies in their core architectures. Next, Nuxt, and SvelteKit are built with complex, runtime browser rendering engines — a massive overhead for content-driven websites with minimal interactive elements. This approach introduces unnecessary mental overhead for developers, forcing them to constantly juggle the cognitive load of writing code that must execute flawlessly in both server and browser environments. It also increases the overall computational complexity and page size for the final visitor.
Astro takes a bold and clear position: it is firmly focused on server-side and static generation. Unlike its competitors, Astro categorically refuses browser-side rendering. It doesn't just minimize client-side rendering — it eliminates it entirely. And this goes exactly towards our goal: a simple mental model.
Wait, why not just use PHP then? 👀
Because web development isn't black and white. No site can be completely absent of JavaScript — certainly not ours! And when you need to break free from pure server-side rendering and add interactive elements, Astro provides a seamless solution that PHP simply cannot match.
Having Vite as its engine, Astro allows you to effortlessly insert JavaScript code, handling all the bundling for you. Moreover: if for particular areas of the page you need strong browser-side interactivity, Astro allows you to insert entire interactive "islands" of React/Vue/Svelte, limiting hydration only to those specific parts.
That's exactly what we, and most content-driven websites, need: the performance and simplicity of server-side rendering for the vast majority of the pages, combined with targeted, rich interactivity where necessary.
(Also, as you may recall, one of our goals was to incorporate a form of safety net through typed languages. PHP/Ruby/Python are not inherently typed, plus lack advanced tools like TypeScript for automatically managing the typing of GraphQL queries. So yeah, PHP will need to take a backseat for now. 🤷♂️)
The new stack
Let's analyze it point by point what we came out with:
The new site is written in Astro and uses the Node adapter, meaning the final output of a build is a Node.js server that needs to run on a physical server. Astro generates new server-side responses for each incoming request.
The server in question is a VPS on Hetzner. The app deployment is managed by Kamal, and can occur manually from the command line or via GitHub Actions. With Hetzner's outrageous prices, you can acquire all the necessary hardware for the task, and more, for just €15/month.
The domain
www-draft.datocms.com
points directly to the Astro server.The domain
www.datocms.com
, on the other hand, points to Fastly, a CDN that supports surrogate keys. Fastly useswww-draft.datocms.com
as the origin.Astro pages, depending on whether the request host is either
www-draft
orwww
, will execute GraphQL requests to DatoCMS with theX-Include-Drafts
header either active or not. This allows our team to access draft content while regular visitors do not.Astro also leverages DatoCMS Cache Tags to cache pages on Fastly indefinitely. In practical terms, Astro reads the
X-Cache-Tags
header for each GraphQL request made to DatoCMS, and applies those tags identically in its own response via theSurrogate-Key
header.
There's only one missing piece: cache invalidation. DatoCMS, through webhooks, sends cache invalidation tags to the Astro server with every content change on the CMS. Astro then uses those tags to purge the Fastly cache via an API call:
How did it go?
Honestly, we cannot be happier about the final result:
Astro's support for TypeScript is excellent, and in general, it has allowed us to do everything we needed, even when we had to go outside "the norm".
The mental model of the architecture is extremely simple: everything's server-side generated. Period. No re-hydration, no huge client-side JavaScript bundles.
Our team can work on the content and see the results in real-time as they save their drafts through Web Previews and Real-time Updates.
End visitors always get cached content from CDN (thanks to
stale-while-revalidate
), so the response is immediate.Thanks to cache tags, cache purging is laser-precise, ensuring that only the pages that truly require updates are invalidated...
...yet, developers do not need to take any action to manage all the traditional complexity of caching. Once again, the mental model is straightforward, to the extent that you can forget about it.
Thanks to Fastly, cache purging happens in less than 150ms: if we accidentally publish incorrect content, we can correct it instantly, without having to wait minutes for a build to complete.
Complete builds and cache purging only occur when a new version of the repo is pushed. In our case, the total build time is approximately 3 minutes. We can probably improve this if we commit ourselves... the important feature, however, is that this time is independent of the number of pages on the site.
Last, but not least: hosting costs are ridiculously low.
How bad was the actual migration?
Our site is about a hundred different sections: from the beginning of the first tests to the full release, approximately two and a half months have passed. Not bad, considering it was never a full-time commitment, and that it has only been Marco and me working on it.
Astro's JSX-like syntax made it pretty easy to transition all the components. Only the more complex components have remained in React as Astro Islands, while small interactivity (useState
, useEffect
, etc.) has been converted into Web Components fairly easily.
"I want the juicy details!"
Good, because we can't wait to talk to you about it! 😜 After this first introduction to the overall architecture of the new site, the upcoming articles will explore in detail many practical topics related to Astro in conjunction with DatoCMS, and how we achieved clear, clean, and highly maintainable code.
These are just a few of the topics we want to discuss:
Typed GraphQL schema;
GraphQL Fragment Colocation;
URL Building / SEO Management / Sitemaps;
Error management;
Astro Actions;
Replacing React with Web Components;
View Transitions;
SVGs;
Whether you're considering a similar migration or just curious about modern web development best practices, we hope our journey inspires and informs yours. See you in the next episode! 👋