Astro 6→7 on a 1,558-post blog: keeping remark/rehype and a middleware gotcha
Contents
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/ Node22.22.0/ pnpm /output: 'static', deployed on Vercel - Posts (
.md/.mdx): 1,558 .astrocomponents: 109 files- Heavily customized markdown pipeline
remarkPlugins:remark-mathrehypePlugins:rehype-raw/rehype-katex/rehype-external-links/ customrehype-youtube-embed/ customrehype-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
| Change | What it does |
|---|---|
| Rust compiler | Rewritten 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ätteri | The 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 + Rolldown | esbuild + Rollup are unified into a single Rust bundler, Rolldown |
| Node v22 required | The 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.
| Package | Current | Astro 7 version | Note |
|---|---|---|---|
| astro | 6.1.3 | 7.0.4 | major bump |
| @astrojs/vercel | 10.0.4 | 11.0.0 | major required (v11’s peerDependencies want astro ^7) |
| @astrojs/markdown-remark | none | 7.2.0 | new dependency (see below) |
| @astrojs/sitemap | 3.7.2 | 3.7.3 | minor |
| @astrojs/rss | 4.0.18 | 4.0.19 | minor |
| @astrojs/partytown | 2.1.6 | 2.1.7 | minor |
| @tailwindcss/vite | 4.1.18 | 4.3.2 | update needed (already supports Vite 8) |
| tailwindcss | 4.1.18 | 4.3.2 | same |
Two things stand out.
@astrojs/vercelv11’speerDependencieswant astro^7. v10 won’t install against Astro 7, so it needs a major bump.@tailwindcss/vite4.3.2 supportsvite: ^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.