Skip to content
Rezha Julio
Go back

Upgrading to Astro 7: A Real-World Migration From a Blog That Already Worked

5 min read

Astro 7 shipped on June 22, 2026. I waited until 7.0.7 to avoid the early-patch churn, then upgraded this blog yesterday. Three files changed. Here’s what happened.

Table of contents

Open Table of contents

Why bother when Astro 6 works

Astro 6 was fine. The blog built, deployed, served. Nothing was on fire.

I upgraded anyway because the parts of Astro 7 that matter are infrastructure, not features:

ChangeWhat it does
Vite 8 + RolldownReplaces Rollup + esbuild with one Rust bundler. Astro’s benchmarks put it at 15-61% faster builds. Gerald Chen measured no speedup on a 48-page blog, so YMMV.
Rust compilerReplaces the Go compiler. Faster, smaller memory footprint. Not user-visible unless you’re hitting build OOMs.
SätteriRust markdown pipeline. Doesn’t run remarkPlugins/rehypePlugins out of the box (see below).
Queued renderingPages render in parallel batches. Invisible on small sites.

Astro 6 will be the previous major in a few months and the plugin ecosystem is already moving. That’s the real reason.

The migration in three diffs

1. package.json — bumped and dropped

package.json diff
- "@astrojs/preact": "^5.1.5",
- "@astrojs/rss": "^4.0.18",
- "@digi4care/astro-google-tagmanager": "^1.6.0",
- "astro": "^6.4.7",
- "astro-expressive-code": "^0.41.7",
+ "@astrojs/preact": "^6.0.1",
+ "@astrojs/rss": "^4.0.19",
+ "astro": "^7.0.7",
+ "astro-expressive-code": "^0.44.0",

Two things happening. First, the straightforward version bumps. Second, I dropped @digi4care/astro-google-tagmanager entirely. The package was used in exactly one place: <SiteVerification id="..." />, which renders as a single <meta name="google-site-verification" content="..."> tag. That’s not a dependency, that’s a line of HTML.

2. astro.config.ts — the unified processor shim

This was the one part I had to think about. Astro 7 defaults to Sätteri for markdown processing. Sätteri is faster, doesn’t run on Node, and doesn’t understand my existing remark/rehype plugins. My config uses four of them: remark-toc, remark-collapse, rehype-slug, rehype-autolink-headings.

Warning

Skipping the @astrojs/markdown-remark shim will silently drop your remarkPlugins and rehypePlugins. Sätteri runs first; the JS pipeline never executes. Astro 7 fails fast with a clear error during config validation, but the fix is the same: install @astrojs/markdown-remark and wrap your plugins in unified({...}).

The fix is one extra import and one wrapper:

astro.config.ts diff
+import { unified } from "@astrojs/markdown-remark";
import remarkToc from "remark-toc";
import remarkCollapse from "remark-collapse";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
markdown: {
- remarkPlugins: [remarkToc, [remarkCollapse, { test: "Table of contents" }]],
- rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: "wrap" }]],
+ processor: unified({
+ remarkPlugins: [remarkToc, [remarkCollapse, { test: "Table of contents" }]],
+ rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, { behavior: "wrap" }]],
+ }),
},

@astrojs/markdown-remark ships with Astro 7 for exactly this case. It’s the same unified pipeline Astro 5 and 6 used, wrapped as an explicit processor. Install it once, wrap once, done.

I could have migrated to Sätteri’s native mdastPlugins/hastPlugins and dropped the four npm packages. I didn’t, for two reasons. First, the plugins are still maintained and the unified wrapper adds no measurable build time on a site this size. Second, I’d rather write one config block than learn a new plugin API and rewrite the ToC behavior I already have. If remark-toc dies or Sätteri becomes mandatory, I’ll deal with it then.

3. src/layouts/Layout.astro — the inlined meta tag

Layout.astro diff
-import { SiteVerification } from "@digi4care/astro-google-tagmanager";
-
- <SiteVerification id="your-google-site-verification-token" />
+ <meta name="google-site-verification" content="your-google-site-verification-token" />

That’s the entire GTM integration in this repo. One import, one meta tag. Inlining cost nothing.

One bonus fix: the caddy code fence

After upgrading, astro check warned that a code block in my Caddy post used caddy as its language, and Shiki doesn’t ship a Caddy grammar. The block was already rendering as plain text because the fallback fired silently in Astro 6. Astro 7 turned it into a real warning.

No community grammar exists for Shiki, as far as I could find. Caddyfile syntax borrows heavily from nginx, so I switched the fence:

2026-adding-brotli-compression-to-caddy.md
```caddy title="Caddyfile"
```nginx title="Caddyfile"

Same block, real highlighting. One line.

What I didn’t migrate

  • Sätteri-native plugins. The unified({...}) shim keeps the JS pipeline. Faster to migrate, slower than native. I’ll revisit if/when remark-toc stops working.
  • src/fetch.ts advanced routing. New in Astro 7, opt-in. I don’t need a custom request pipeline for a static blog served by Caddy.
  • CDN cache providers. Astro 7 added experimental providers for Netlify, Vercel, Cloudflare. Site is on a VPS. No value.
  • JSON logger for AI agents. Detects coding agents, emits structured JSON logs. Cute. I don’t run agents against this site in a way that would benefit.
  • Rolldown custom plugin API. I have no custom Vite plugins. Nothing to migrate.

If you’re on Astro 6 today

Run bunx @astrojs/upgrade and read the output. For an AstroPaper-style blog with remark/rehype plugins, the only real decision is whether to install @astrojs/markdown-remark and keep the unified pipeline, or migrate to Sätteri’s plugin API. The first option is ten minutes of work. The second is a few hours and unlocks the full speedup.

Tip

bunx @astrojs/upgrade bumps Astro and its official integrations together. It will not bump third-party integrations like @digi4care/astro-google-tagmanager (which is why I’m mentioning it — I’ve been bitten before). After running it, audit package.json manually.

I picked the first. The blog is small, the build was already fast, and I had other things to do.


Related Posts


Previous Post
Why I Am Leaving Duolingo