Skip to content

Upgrade Astro 4 → 7 (with Tailwind 3 → 4 migration) - #11

Merged
behitek merged 8 commits into
mainfrom
upgrade/astro-7
Aug 11, 2026
Merged

Upgrade Astro 4 → 7 (with Tailwind 3 → 4 migration)#11
behitek merged 8 commits into
mainfrom
upgrade/astro-7

Conversation

@behitek

@behitek behitek commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

Upgrades the site from Astro 4.16.19 to the latest Astro 7.2.0, crossing three major version
boundaries (4→5→6→7), each landed as its own verified, revertable commit:

  • Prep — bump CI to Node 22 (Astro 6/7 require ≥22.12), add an engines field, drop an unused
    dependency.
  • Astro 5 — migrate src/content/config.tssrc/content.config.ts onto the Content Layer
    API (loader: glob(...)), updating every call site from the legacy .slug/.render() entry API
    to .id/render(post).
  • Tailwind 3 → 4@astrojs/tailwind was never published for Astro 6/7 (its peer range tops
    out at ^5.0.0), so this had to land before the next hop. Ran the official
    @tailwindcss/upgrade codemod, swapped the integration for @tailwindcss/vite, and converted
    tailwind.config.mjs into a CSS @theme block.
  • Astro 6 — dependency bump only; verified the i18n.routing.redirectToDefaultLocale
    default flip and the markdown heading-slug generation change are both no-ops for this site.
  • Astro 7 — the default Markdown processor changes to a new Rust-based one ("Sätteri");
    installed @astrojs/markdown-remark explicitly to keep the existing remarkDirective +
    custom admonitions plugin + Shiki config working byte-for-byte.
  • Cleanup — updated CLAUDE.md for the new stack, applied a safe npm audit fix (0
    vulnerabilities remaining, down from 26 at the start).

At every phase, rendered HTML/RSS/sitemap output was diffed against the prior phase's build. The
only differences anywhere in the final output vs. the pre-upgrade baseline are cosmetic and
expected: the Astro version string, per-build asset hashes, value-equivalent Tailwind utility
renames from the codemod (e.g. leading-[1.5]leading-normal), Astro 7's stricter default
HTML whitespace compression, and the new Vite 8/Rolldown minifier's inline-script syntax style.
Admonitions, heading anchors, Shiki code-block metadata, RSS, and the sitemap are byte-identical
throughout.

Test plan

  • npm run build (astro check + astro build) passes cleanly at every phase
  • All 40 pages across both locales (/ and /en/ trees) return 200 on a production preview
    build
  • Admonitions (:::tip, :::warning, :::danger, :::note, :::info), heading anchor IDs,
    and the Shiki copy-button wrapper are unchanged from the pre-upgrade baseline
  • RSS feed (/rss.xml) and sitemap (/sitemap-index.xml) output are byte-identical to baseline
  • npm audit reports 0 vulnerabilities (down from 26)
  • Visual review of Tailwind 4 output in a real browser (automated diffing covered HTML/CSS
    output; a human pass over the brutalist design — borders, color ramps, responsive collapse
    at 900px, blink-cursor animation — is still worth doing before merge)

🤖 Generated with Claude Code

…ead dependency)

Bump CI Node from 20 to 22 ahead of Astro 6/7's Node >=22.12 requirement,
pin the same floor via package.json engines, and drop the unused
remark-admonitions dependency (the actual admonitions logic is the
custom remarkAdmonitions() function already inline in astro.config.mjs).
Bump astro to 5.18.2 and @astrojs/mdx to 4.3.14 (mandatory at v5).
Rewrite src/content/config.ts -> src/content.config.ts using loader-based
collections (glob()) instead of the removed type: 'content'|'data' shape,
and update every call site from the legacy .slug/.render() entry API to
.id/render(post). Verified rendered HTML, RSS, and sitemap output are
byte-identical to the pre-upgrade Astro 4 build (aside from the Astro
version string and per-build asset hashes).
Bump astro to 5.18.2 and integrations (@astrojs/mdx to 4.3.14, @astrojs/rss
to 4.0.19, @astrojs/sitemap to 3.7.3, @astrojs/check to 0.9.10). Rewrite
content.config.ts collections to use loader-based glob() instead of the
removed type: 'content'|'data' shape, and update every call site from the
legacy .slug/.render() entry API to .id/render(post). Verified rendered
HTML, RSS, and sitemap output are byte-identical to the pre-upgrade Astro 4
build (aside from the Astro version string and per-build asset hashes).
…css/vite

@astrojs/tailwind was never published for Astro 6/7 (peer range tops out
at ^5.0.0), so it must go before the next hop. Ran the official
@tailwindcss/upgrade codemod: converts tailwind.config.mjs into a CSS
@theme block in global.css (preserving all custom color ramps, fonts,
zeroed border-radius, and the blink keyframe), migrates @layer components
classes to @Utility blocks, and renames affected utility classes across
16 templates (outline-none->outline-hidden, backdrop-blur-sm->-xs, etc).
Swapped the @astrojs/tailwind integration for @tailwindcss/vite's Vite
plugin in astro.config.mjs, and added @reference imports to the two
scoped <style> blocks that use @apply (Tailwind 4 requires it per CSS
module). Verified rendered HTML is unchanged aside from the codemod's
value-equivalent utility renames (e.g. leading-[1.5] -> leading-normal).
Bump astro to 6.4.8 and @astrojs/mdx to 6.0.3. No code changes required:
the legacy content collections API and @astrojs/tailwind (the two things
v6 removes/breaks) were already migrated away from in prior phases.

Verified behaviorally: the i18n.routing.redirectToDefaultLocale default
flip (true->false) is a no-op here since /en/ is a manually-duplicated
page tree rather than Astro-generated i18n routing (/ , /en, and /en/ all
still resolve 200 with no unexpected redirects). Markdown heading-slug
generation is unchanged for existing posts (no diff in rendered <h2-4 id>
attributes). Rendered output otherwise matches the Tailwind-4 baseline
aside from a stricter/more spec-correct &amp; entity escaping in one
template's href attribute.
…rojs/markdown-remark

Bump astro to 7.2.0 and @astrojs/mdx to 7.0.5. Astro 7 replaces the
default Markdown processor with a new Rust-based one ("Satteri") and no
longer installs @astrojs/markdown-remark by default. Installed it
explicitly (7.2.2) to keep the existing unified()/remark pipeline
(remarkDirective + the custom remarkAdmonitions() plugin, plus the Shiki
shikiConfig transformer) working byte-for-byte as before, rather than
porting to Satteri's less-documented native plugin API.

Verified: admonition markup, heading anchor ids, Shiki code-block-wrapper
metadata, RSS, and the sitemap are all unchanged from the Astro 6 build.
The only differences anywhere in rendered output are two documented,
cosmetic Astro 7 changes - the stricter default compressHTML behavior
(whitespace between tags/inline text is now stripped more aggressively)
and the new Vite 8/Rolldown minifier's inline-script output style (var
instead of const, backtick strings instead of double-quoted) - neither
changes visible content or functionality.
Update the Stack line, content collections section, and design-system
section to reflect the completed migration: Astro 7 with the
@astrojs/markdown-remark opt-in, Tailwind 4 via @tailwindcss/vite with
CSS-based config (no more tailwind.config.mjs), content.config.ts's
loader-based collections and id-not-slug entries, and @Utility blocks
replacing @layer components.

Also ran `npm audit fix` for a transitive mdast-util-to-hast advisory
surfaced by the markdown-remark pipeline (patch-level bump, no API
change) - 0 vulnerabilities remaining.

Full regression pass: all 40 pages across both locales return 200 on a
production preview build.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 11, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
blog 4954f0d Aug 11 2026, 06:41 AM

The lockfile had been regenerated under Node 25/npm 11 locally, which
resolved lightningcss's optional platform dependencies differently than
npm 10 (bundled with the Node 22 CI uses) - npm ci failed on CI with
"Missing: @emnapi/runtime, @emnapi/core from lock file". Reproduced
locally under Node 22 via nvm and reinstalled to fix.
@behitek
behitek merged commit 9ba34cb into main Aug 11, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant