Tech7 min read

Astro 6→7 on a 1,558-post blog: keeping remark/rehype and a middleware gotcha

IkesanContents

Astro 7 shipped on June 22, 2026. The .astro compiler was rewritten from Go to Rust, Vite jumped to 8 (Rolldown), and the markdown pipeline was replaced by the native Sätteri. This blog runs on Astro 6.1.3: 1,558 posts, KaTeX for math, custom rehype plugins for YouTube/video embeds, deployed on Vercel. The more a site touches its markdown pipeline, the more Sätteri affects it, so I actually ran the upgrade and kept a work log.

Bumping the dependencies and fixing one .astro file was all it took for the production build to pass. Below is the procedure and the spots the Rust compiler rejected.

This setup

  • Astro 6.1.3 / Node 22.22.0 / pnpm / output: 'static', deployed on Vercel
  • Posts (.md/.mdx): 1,558
  • .astro components: 109 files
  • Heavily customized markdown pipeline
    • remarkPlugins: remark-math
    • rehypePlugins: rehype-raw / rehype-katex / rehype-external-links / custom rehype-youtube-embed / custom rehype-video-embed
    • A custom Shiki transformer (prints the file name on code blocks)

In other words, the default markdown processing is heavily replaced. That’s exactly where Astro 7’s changes land hardest.

Breaking changes that hit this blog

ChangeWhat it does
Rust compilerRewritten from Go to Rust. Unclosed tags and invalid HTML nesting (like a <div> inside a <p>) now raise an error instead of being silently fixed the way the old compiler did
SätteriThe default processing for .md/.mdx moves from remark/rehype to the native Sätteri. remark/rehype plugins don’t run out of the box
Vite 8 + Rolldownesbuild + Rollup are unified into a single Rust bundler, Rolldown
Node v22 requiredThe requirement is now >=22.12.0, which 22.22.0 satisfies

Checking dependency compatibility

I checked each package’s Astro 7-compatible version and peerDependencies on the registry first.

PackageCurrentAstro 7 versionNote
astro6.1.37.0.4major bump
@astrojs/vercel10.0.411.0.0major required (v11’s peerDependencies want astro ^7)
@astrojs/markdown-remarknone7.2.0new dependency (see below)
@astrojs/sitemap3.7.23.7.3minor
@astrojs/rss4.0.184.0.19minor
@astrojs/partytown2.1.62.1.7minor
@tailwindcss/vite4.1.184.3.2update needed (already supports Vite 8)
tailwindcss4.1.184.3.2same

Two things stand out.

  • @astrojs/vercel v11’s peerDependencies want astro ^7. v10 won’t install against Astro 7, so it needs a major bump.
  • @tailwindcss/vite 4.3.2 supports vite: ^5 || ^6 || ^7 || ^8, so Vite 8 is fine.

The main hurdle is Sätteri vs remark/rehype

Astro 7 processes .md/.mdx with Sätteri. The remark/rehype pipeline is off by default. This blog runs math, raw HTML, and custom embeds all through rehype, so on bare Sätteri everything breaks.

There are two options. The first is porting the plugins to Sätteri: rewrite the existing remark/rehype plugins as Sätteri MDAST/HAST plugins. Given how much behavior is baked in, that’s a lot of work. The second is staying on the unified pipeline by installing @astrojs/markdown-remark@7.2.0, which lets you keep using the existing remarkPlugins/rehypePlugins config as-is. It prints a deprecation warning but works.

I went with the second. Keeping the math/embed/raw-HTML behavior of 1,558 posts is the priority, and I want to avoid rendering diffs from porting. Porting is a separate task for later.

The actual work

1. Create a branch

git checkout -b upgrade-astro-7

2. Update package.json

Rewrite the dependencies. @astrojs/markdown-remark is a new addition.

-    "@astrojs/partytown": "^2.1.6",
-    "@astrojs/rss": "^4.0.18",
-    "@astrojs/sitemap": "^3.7.2",
-    "@astrojs/vercel": "^10.0.4",
+    "@astrojs/markdown-remark": "^7.2.0",
+    "@astrojs/partytown": "^2.1.7",
+    "@astrojs/rss": "^4.0.19",
+    "@astrojs/sitemap": "^3.7.3",
+    "@astrojs/vercel": "^11.0.0",
-    "astro": "^6.1.3",
+    "astro": "^7.0.4",

Bump the devDependencies too.

-    "@tailwindcss/vite": "^4.1.18",
+    "@tailwindcss/vite": "^4.3.2",
-    "tailwindcss": "^4.1.18",
+    "tailwindcss": "^4.3.2",

3. Install

pnpm install

The swap went through as expected.

dependencies:
+ @astrojs/markdown-remark 7.2.0
+ @astrojs/partytown 2.1.7
+ @astrojs/rss 4.0.19
+ @astrojs/sitemap 3.7.3
+ @astrojs/vercel 11.0.0
+ astro 7.0.4

devDependencies:
+ @tailwindcss/vite 4.3.2
+ tailwindcss 4.3.2

It installed with no peer dependency warnings. Because @astrojs/markdown-remark is installed first, markdown.remarkPlugins/markdown.rehypePlugins should resolve the same as before.

Build verification

This blog serves its dev server remotely over Tailscale, so building drops the dev server and cuts remote access. I stopped dev first, then ran pnpm build.

The Rust compiler rejected exactly one spot.

[CompilerError] Closing tag '</a>' has no matching opening tag.
  Location: src/pages/harahe.astro:243:32

That’s the cause: a </a> with no opening tag had slipped in. The old Go compiler silently dropped it; the Rust compiler errors.

-                  <li>表紙イラスト各位</a></li>
+                  <li>表紙イラスト各位</li>

Deleting one line and rebuilding passed.

[build] 5316 page(s) built in 21.26s
[pagefind-articles] Pagefind indexed 1552 article pages
[@astrojs/vercel] Copying static files to .vercel/output/static
[build] Complete!

There are 109 .astro files, and only one tripped. The whitespace handling and CSS serialization diffs I’d worried about didn’t produce visible breakage as far as I eyeballed. Math, embeds, and raw HTML show no rendering diffs so far either, probably because avoiding Sätteri (@astrojs/markdown-remark) keeps the unified pipeline in place.

Here’s the warning during the build.

markdown.remarkPlugins, markdown.rehypePlugins, and markdown.remarkRehype are deprecated.
Pass them to unified({...}) from @astrojs/markdown-remark directly instead.

It’s a warning you get as long as you stay on the unified pipeline, with no functional impact. The proper long-term move is to pass the plugins directly to unified() from @astrojs/markdown-remark.

One more: Vite/Rolldown warned about a chunk larger than 500 kB. Looking inside, the cause is a single library, heic2any (the HEIC conversion library used by the image-converter lab tool), at 1.3 MB on its own. It’s an independent chunk that never loads on article pages, and it’s not Astro 7-specific (Vite’s default threshold has been 500 kB all along). No real impact, so I left it.

Checking the dev side

I brought dev back up and hit the main routes. With trailingSlash: 'never', trailing-slash URLs correctly 404, and slashless ones all return 200.

200  /
200  /en
200  /en/articles
200  /en/articles/10secondsdash-release
200  /en/tech
200  /articles

The JA top, EN top, articles, listings, and tags all render fine.

Astro 7 added agent detection. When you start astro dev under a coding agent, it auto-persists in the background, and subsequent starts return already running as structured JSON. That made restarting dev smooth this time.

Checking on a Vercel preview

Before merging into main, I push the branch and check it on a Vercel preview deployment. Pushing a Git branch auto-creates a preview URL separate from production, so you get one build through the same path as production.

The preview build itself passed, but a note that never appeared locally showed up in the Vercel log.

The middleware TS1543 vs esbuild bind

This was in the Vercel build log.

middleware.ts(6,20): error TS1543: Importing a JSON file into an ECMAScript module
requires a 'type: "json"' import attribute when 'module' is set to 'NodeNext'.

This blog keeps a middleware.ts (Vercel routing middleware) at the repo root, and it imports tag-map.json there. Vercel type-checks this under NodeNext, so it wants an import attribute on the JSON import.

I followed the instruction and added the import attribute first.

-import tagMap from './tag-map.json';
+import tagMap from './tag-map.json' with { type: 'json' };

That satisfied the type check, but then esbuild failed at the final deploy stage.

Build failed with 1 error:
vc-file-system:__vc__ns__/0/middleware.js:5:36: ERROR: Expected ";" but found "with"

The esbuild that Vercel uses to bundle the middleware can’t parse this JSON import-attribute syntax yet and errors on with. TypeScript demands it; the bundler won’t accept it.

The original TS1543 is a type diagnostic that doesn’t stop the deploy, and adding the import attribute is what actually broke the deploy. I reverted to a plain import and suppressed only the type diagnostic with @ts-ignore.

// @ts-ignore
import tagMap from './tag-map.json';

That clears TS1543 from the Vercel log, and the esbuild bundle passes.