🔥 Discover this awesome post from Hacker News 📖
📂 **Category**:
✅ **What You’ll Learn**:
<span>Hellospan>🔥<span>Worldspan>
Moving to Rust allowed us to ship native binaries for supported platforms, with a WASM fallback for environments that need it. This pattern is now standard in the JavaScript tooling world, used by projects such as Rolldown and Lightning CSS. Under the hood, the new compiler is built on oxc for parsing and Lightning CSS for CSS scoping.
In isolation, the Rust compiler showed a roughly 6% improvement in build times on https://docs.astro.build. That’s because .astro compilation is rarely the bottleneck; Markdown processing and bundling typically dominate build time. But every bit adds up, especially on large sites with thousands of pages, and the compiler’s gains compound with the other performance improvements in this release.
Markdown & MDX in Rust
Astro 7 replaces the default Markdown and MDX pipeline with Sätteri, a Rust-powered processor created by Astro core team member
Erika
. Switching the Astro docs and Cloudflare docs builds to Sätteri shaved over a minute off their build times, making Markdown-heavy sites the biggest winners in Astro 7.
Until now, Astro’s Markdown pipeline ran on unified (remark, rehype, and a long tail of JavaScript dependencies). On large sites with thousands of pages, that pipeline was often the slowest phase of the build: each file parsed through JavaScript, run through plugin after plugin over the full AST, then serialized back to HTML. In Astro 6.4, we made the pipeline pluggable and shipped Sätteri as an opt-in alternative. Astro 7 makes it the default.
Under the hood, Sätteri uses pulldown-cmark for CommonMark parsing and Oxc for MDX expression parsing, both native Rust. It ships platform-specific binaries with a WASM fallback, the same approach used by the new .astro compiler. Speed isn’t the only win, though. Sätteri also implements many Markdown features natively that previously required separate plugins:
| Feature | unified | Sätteri |
|---|---|---|
| GFM (tables, footnotes, strikethrough, task lists) | remark-gfm plugin | Built-in, on by default |
| Smart punctuation (curly quotes, em dashes) | remark-smartypants plugin | Built-in |
| Heading IDs | remark-heading-id or similar plugin | Built-in |
| Container directives | remark-directive plugin | Built-in |
| Math | remark-math plugin | Built-in |
| Frontmatter (YAML, TOML) | remark-frontmatter plugin | Built-in |
| Superscript / subscript | remark-supersub or similar plugin | Built-in |
| Wikilinks | remark-wiki-link plugin | Built-in |
Non-default features are enabled through the features option:
import 🔥 from 'astro/config';
import 🔥 from '@astrojs/markdown-satteri';
export default defineConfig({
Sätteri has its own plugin API too. Plugins declare which node types they care about and skip the rest, instead of walking the entire tree on every pass. This makes adding plugins much cheaper than in unified, where every plugin traverses every node.
If you depend on remark or rehype plugins, the unified-based pipeline is still available via @astrojs/markdown-remark:
import 🔥 from 'astro/config';
import { unified } from '@astrojs/markdown-remark';
import remarkToc from 'remark-toc';
export default defineConfig({
remarkPlugins: [remarkToc],
Learn more about Sätteri’s features and plugin API at satteri.bruits.org.
Queued Rendering
Queued rendering was introduced in Astro 6.0 as an experimental option and is now stable and the default rendering engine. It’s ~2.4× faster1 in speed!
import { defineConfig } from "astro/config";
export default defineConfig({
Previously, Astro rendered pages using a recursive approach where children were rendered using the same render* function, like in the following pseudocode:
export function renderComponentToString(node: unknown): string {
destination += `<${node.name}>`; // opening tag
for (const child of node.children) {
// Here's where we recurse the children by calling renderComponentToString
destination += renderComponentToString(child);
destination += `${node.name}>`; // closing tag
The new engine uses a queue (or stack) and a single loop. The queue is populated with child nodes in the correct order, and the loop keeps rendering nodes until the queue is drained.
The following pseudocode is an oversimplified version of the real deal, but it should provide a clear picture:
export function renderComponentToString(root: unknown): string {
destination += `<${root.name}>`; // opening tag
// this is our queue, populated and flushed as we render
while (stack.length > 0) {
const node = stack.pop();
if (Array.isArray(node)) {
// The nodes at the very end must be rendered to destination first
for (let i = node.length - 1; i >= 0; i--) stack.push(node[i]);
const nodeType = typeof node;
if (nodeType === 'string') {
destination += escapeHTML(node as string);
destination += `${root.name}>`; // opening tag
The first implementation of the strategy worked on a two-pass phase: create an ordered list of components (nodes), and loop the list and render the components.
The new implementation doesn’t create a full list anymore, instead the list is rendered (flushed) while it’s looped. This final approach is faster than the first iteration, and needs less memory compared to the recursive approach.
Advanced Routing
Astro started as a static site generator with file-based routing. Over time, features like middleware, redirects, rewrites, Actions, sessions, and i18n gave Astro apps more server-side power, but they also made the request lifecycle harder to control. If you needed auth to run before Actions, logging to wrap only page rendering, or a non-Astro API to handle some requests first, you had to work around the pipeline instead of composing it directly.
In Astro 7, you can now take full control over Astro’s request pipeline by adding a src/fetch.ts file to your project. This file exports the standard fetch handler pattern popularized by Cloudflare Workers, Deno, and Bun.
import { astro, FetchState } from 'astro/fetch';
fetch(request: Request) {
const state = new FetchState(request);
// Forward API requests to a backend service
if (state.url.pathname.startsWith('/api')) {
const url = new URL(state.url.pathname + state.url.search, 'https://backend-api.example.com');
return fetch(new Request(url, request));
// Fallback to Astro pages/endpoints
The API is also compatible with Hono, allowing you to bring Hono middleware into your Astro application:
import { astro } from 'astro/hono';
import { Hono } from 'hono';
import { basicAuth } from 'hono/basic-auth';
app.use(basicAuth({ username: 'admin', password: 'secret' }));
For advanced usage, you can compose individual Astro features as separate middleware, giving you full control over the request pipeline. If you’ve ever used Astro middleware and been frustrated that your auth check ran after Astro Actions, or that you couldn’t log response timing without wrapping everything yourself, you can now put your code exactly where it needs to be:
import { Hono } from 'hono';
import { actions, middleware, pages, i18n } from 'astro/hono';
import { auth } from './middleware/auth';
import { timing } from './middleware/timing';
app.use(auth()); // Auth runs before actions, no unauthenticated calls
app.use(timing()); // Timing wraps only page rendering
If you don’t add a src/fetch.ts file, Astro behaves exactly as it does today.
Route Caching
Caching on-demand rendered responses is harder than it should be. Every host does it differently, and there has never been a standard way to control it from your application code. Astro 7 introduces route caching to solve this. First released experimentally in Astro 6, the feature is now stable and adds a single platform-agnostic API for caching: set directives in your routes, and Astro handles the rest wherever you deploy.
You configure a cache provider once, then use Astro.cache in your pages (or context.cache in API routes and middleware) to control caching per response, based on standard HTTP caching semantics. Astro ships with a built-in memoryCache() provider to get you started:
import { defineConfig, memoryCache } from 'astro/config';
export default defineConfig({
maxAge: 120, // Cache for 2 minutes
swr: 60, // Serve stale for 1 minute while revalidating
tags: ['products'], // Tag for targeted invalidation
You can also define caching rules for groups of routes declaratively in your config with routeRules, keeping caching out of your route code entirely:
export default defineConfig({
cache: { provider: memoryCache() },
'/blog/[...path]': { maxAge: 300, swr: 60 },
Where route caching really shines is its integration with live content collections. A live loader can attach a cache hint to the data it returns, with tags for invalidation and a last-modified time for freshness. Pass that entry straight to Astro.cache.set() and Astro reads the hint for you, no manual headers required:
import { getLiveEntry } from 'astro:content';
const { entry } = await getLiveEntry('products', Astro.params.id);
// Astro reads the loader's cache hint from the entry:
Cached responses can be purged on demand with cache.invalidate(), by tag or by path. For example, you can expose a webhook endpoint for your CMS to call whenever content changes. This maps to each provider’s invalidation API, clearing every affected response without a rebuild:
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request, cache }) => {
// A real implementation would validate the request and check a secret token before invalidating.
const { slug } = await request.json();
// Invalidate every response tagged 'products'...
await cache.invalidate({ tags: ["products"] });
// ...invalidate every page that used a particular entry...
await cache.invalidate({ tags: [`products:${slug}`] });
// ...or purge a single path directly.
await cache.invalidate({ path: `/products/${slug}` });
return new Response('Revalidated');
If you tried route caching while it was experimental, the only change is to move cache and routeRules out of the experimental block to the top level of your config. The API is otherwise unchanged. See the route caching guide for the full reference.
CDN Cache Providers
When route caching shipped in Astro 6, it came with a single in-memory provider to use with the Node adapter. Astro 7 adds experimental CDN providers for Netlify, Vercel, and Cloudflare (in private beta).
Rather than storing responses in memory, these providers push your caching directives down to the host’s edge network, for even faster responses. Cache hits are then served straight from the CDN, without invoking your server function at all.
In a future release, these providers will be enabled automatically, but during the experimental phase you should add them manually. Import the provider for your adapter and set it as your cache provider:
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';
import { cacheNetlify } from '@astrojs/netlify/cache';
export default defineConfig({
provider: cacheNetlify(),
Each adapter exports a provider from its /cache entrypoint:
| Adapter | Import | Provider |
|---|---|---|
| Netlify | @astrojs/netlify/cache |
cacheNetlify() |
| Vercel | @astrojs/vercel/cache |
cacheVercel() |
| Cloudflare ⚠️ | @astrojs/cloudflare/cache |
cacheCloudflare() |
The same Astro.cache, routeRules, and cache.invalidate() APIs work with every provider. Each one translates your directives into the platform’s native cache-control headers and tag- or path-based purges.
AI Enhancements
AI coding agents are now part of many developers’ workflows, and they need different things from a dev server than humans do. Astro 7 is our first step toward making Astro a better platform for agent-driven development.
Background Dev Server
AI agents struggle with long-running processes. They shell out, wait for exit, and read output, but a dev server never exits. Agents hang, start duplicate servers, lose track of running instances, or leave zombie processes behind. We see this as part of Astro’s automated issue triage as well, with agents sometimes spending more time fumbling with dev servers than testing code.
Astro 7 adds astro dev --background, which starts the dev server as a managed background process. Astro can also detect when it’s running inside an AI agent and enable background mode automatically, so no flags are needed in agent workflows. If no agent is detected, astro dev behaves exactly as before.
Dev server running at http://localhost:4321 (pid 12345)
The command blocks until the server is ready to accept requests, reports the URL and process ID, then detaches. No polling, no sleeping, no parsing terminal output for “Local:”.
A lockfile prevents duplicate instances. If an agent tries to start a second server, it gets back the existing instance’s details instead of spawning a conflicting process:
Dev server already running at http://localhost:4321 (pid 12345)
You can check status and stop the server from separate shell sessions:
Dev server running at http://localhost:4321 (pid 12345, uptime 123s, background)
Stopped dev server (pid 12345).
You can also read the background server’s logs with astro dev logs.
Every command is idempotent and forgiving. Stopping when not running succeeds silently, and starting when already running returns the existing instance. Agents often lose track of process state, and the CLI doesn’t punish them for it.
All running dev servers also expose a /_astro/status health endpoint that agents can query to confirm the server is alive and ready to accept requests.
JSON Logging
Astro’s logger is now fully configurable. For agents, JSON logging is enabled automatically when agent detection turns on background mode. For everyone else, it’s available via the CLI or configuration:
import { defineConfig, logHandlers } from "astro/config";
export default defineConfig({
logger: logHandlers.json()
The new logger API also supports custom log handlers and a compose() API for combining multiple loggers. For example, you can keep human-readable output in the console while also writing JSON logs for tools that need structured output:
import { defineConfig, logHandlers } from "astro/config";
export default defineConfig({
logger: logHandlers.compose(
Learn more about Astro’s support for AI tooling in the AI guide.
The Astro core team is:
Alexander Niebuhr
,
Armand Philippot
,
Chris Swithinbank
,
Emanuele Stoppa
,
Erika
,
Florian Lefebvre
,
Fred Schott
,
HiDeoo
,
Luiz Ferraz
,
Matt Kane
,
Matthew Phillips
,
Reuben Tier
,
Sarah Rainsberger
, and
Yan Thomas
.
Special thanks to everyone who contributed to Astro 7 with code, docs, reviews, and testing, including:
0x K., 0xRozier, AceCodePt, Adam Chalemian, Adam Matthiesen, Adam McKee, Adam Page, Agus Setiawan, Ahmad Yasser, Alejandro Romano, Alex, Alex Dombroski, Alex Launi, Alexander Flodin, Alexis Aguilar, Aly Cerruti, Amar Reddy, Andreas Deininger, Andrei Alba, Antony Faris, Ariel K, Ash Hitchcock, atsbob, B Sai Thrishul, Barry, Ben Limmer, Bernd Strehl, BitToby, btea, Burra Karthikeya, buschtrisha77, Calvin Liang, Cameron Pak, Cameron Smith, Carlos Lázaro Costa, chaegumi, Chan, Chase McCoy, ChrisLaRocque, Ciaran Moran, Corbin Crutchley, CyberFlame, Daniel Bodky, Daniel Lo Nigro, Daniel Zamyatin, Daniil Sivak, Dario Piotrowicz, dataCenter430, Dawid Gaweł, Desel72, dfedoryshchev, Dom Christie, done, Dor Alagem, Dream, Ed Melly, Ed Melly, Edgar, Ellie, Em Poulter, Eric Grill, Eric Mika, Eryk Baran, Eveeifyeve, fabon, farshad, Felipe Arce, Felix Schneider, Felmon, fkatsuhiro, G Taki@MAX, Giray, Gokhan Kurt, Great Journey, Greg L. Turnquist, Harsh Agarwal, Haz, helio-cf, Henri Fournier, Henri Koskenranta, Henry, Igor Koop, Jack Lukic, Jack Moorhouse, Jack Shelton, jahndan, James Basoo, James Garbutt, James Murty, James Opstad, Jason P. Cochrane, Jett Way, Jimmy, joel hansson, John Mortlock, Johnny Noble, Jon Ege Ronnenberg, Joost de Valk, Jordan Demaison, Josh Soref, JPette1783, Julian Wolf, Julien Cayzac, Junseong Park, Justin Francos, Kai, kato takeshi, Kedar Vartak, Kendell, Kevin Brown, knj, Koji Wakamiya, Konrad Szajna, Kristijan, KTrain, Kumar Gautam, Kyle McLean, Lee Freeman, Leif Marcus, Leonie, Lieke, liruifengv, LongYC, Louis Escher, Luke Deen Taylor, MA2153, Mark Ignacio, Martin DONADIEU, Martin Heidegger, Martin Trapp, Matheus Baroni, Mathieu Mafille, Matthew Justice, Matthias, Matthieu Tremblay, Mavik, Max Malkin, maxim, meyer, Michael Giraldo, Mike Pagé, Milo, Minh Lê, Misrilal, MkDev11, Mochammad Farros Fatchur Roji, moktamd, Naren, Natan Sągol, Nicolò Paternoster, Ntale Swamadu, oab24413gmai, ocavue, Oliver Speir, oliverlynch, Ossaid, Patrick Linnane, Peter Philipp, Phaneendra, Pierre G., pierreeurope, Quetzal Rivera, R A, Raanelom, Rafael Yasuhide Sudo, Rahul Dogra, Rayan Salhab, Rodrigo Santos, Rohan Santhosh Kumar, Roman, Roman Kholiavko, Sam Richard, sanchezmaldonadojesusadrian14-coder, Sanjaiyan Parthipan, sanjibani, Schahin, Sebastian Beltran, Sebastien Barre, Shinya Fujino, Stefan Machhammer, Stel Clementine, Tay, Tee Ming, thelazylama, Timo Behrmann, tmimmanuel, Tobias Breit, Tom Callahan, Tomasz Cz-Sokołow, travisBREAKS, Tristan Bessoussa, Umut Keltek, Utpal Sen, Vagno, Varun Chawla, Victor Berchet, Vladyslav Shevchenko, Yagiz Nizipli, yy, and 翠
We hope you enjoy Astro 7. If you run into issues or want to share feedback, please join us on Discord, post on GitHub, or reach out on Bluesky, Twitter, and Mastodon.
{💬|⚡|🔥} **What’s your take?**
Share your thoughts in the comments below!
#️⃣ **#Astro #Astro**
🕒 **Posted on**: 1783450578
🌟 **Want more?** Click here for more info! 🌟
