The Build That Panicked
Date: 2025-04-28 Duration: About an hour Issue: Astro build crashing on Cloudflare Pages Root Cause: Dependency version chaos from using âlatestâ
The Error
panic: html: bad parser state: originalIM was set twice [recovered]
panic: interface conversion: string is not error: missing method Error
Thatâs not a JavaScript error. Thatâs a Go panic. Something deep in the Astro compiler was dying.
The Wild Goose Chase
The error pointed vaguely at HTML parsing. So I went hunting through components.
Attempt 1: Fixed JSX-style comments to HTML comments.
<!-- Before -->
{/* <Fragment set:html={social.icon} /> */}
<!-- After -->
<!-- <Fragment set:html={social.icon} /> -->
Same error.
Attempt 2: Removed SVG icons from social links entirely.
Same error.
Attempt 3: Removed set:html directives from Terminal component.
Same error.
Attempt 4: Created a minimal test layout with almost no content.
Same. Error.
The parser was panicking regardless of what I changed. This wasnât a code problem.
The Real Clue
Buried in the build logs:
npm WARN EBADENGINE Unsupported engine {
package: '[email protected]',
required: { node: '>=18.19' },
current: { node: 'v18.17.1', npm: '9.6.7' }
}
Node 18.17.1 running code that expected 18.19+. A transitive dependency was unhappy.
But that was just a warning. The real culprit was in package.json:
"dependencies": {
"@astrojs/cloudflare": "latest",
"@astrojs/mdx": "latest",
"@astrojs/rss": "latest",
"@astrojs/sitemap": "latest",
"@astrojs/tailwind": "latest",
"astro": "latest"
}
Every single dependency set to latest.
Why âlatestâ Kills Builds
When you deploy to Cloudflare Pages, it runs npm install fresh. If your package.json says latest, npm pulls the newest version at that moment.
The problem:
- I pushed code that worked with Astro 4.5.x
- Two weeks later, Cloudflare ran the build
- npm installed Astro 4.7.x (or whatever was current)
- The new version had a parser bug, or incompatibility, or breaking change
- Build panicked
The code hadnât changed. The dependencies had.
The Fix
Pinned every dependency to exact versions:
"dependencies": {
"@astrojs/cloudflare": "10.2.1",
"@astrojs/mdx": "2.2.1",
"@astrojs/rss": "4.0.5",
"@astrojs/sitemap": "3.1.1",
"@astrojs/tailwind": "5.1.0",
"astro": "4.5.12"
}
Cleared the Cloudflare Pages cache. Redeployed.
Build passed. Site worked.
The Secondary Fix
Also bumped the Node.js version in Cloudflare Pages settings:
Environment Variables:
NODE_VERSION = 18.19.0
Now the build environment matched what the dependencies expected.
Why the Error Message Was Useless
The âoriginalIM was set twiceâ panic came from the Astro compilerâs Go-based HTML parser. When the parser encountered code that worked on one version but not another, it didnât throw a helpful error â it panicked.
The parser didnât know which component caused the issue. It just knew its internal state was corrupted. So it pointed at âHTML parsingâ generically.
I could have commented out every single component in my project and the error would have persisted â because the problem was the parser itself, not my code.
Lessons
Never use âlatestâ in production package.json. Pin versions. Always.
Check npm warnings for version mismatches. The âEBADENGINEâ warning was a clue that dependencies were fighting each other.
Cryptic errors sometimes arenât about your code. When the error message doesnât match what youâre changing, step back. The problem might be environmental.
Cloudflare Pages rebuilds from scratch. Every deploy gets fresh dependencies. If youâre using floating versions, youâre gambling on what gets installed.
The Prevention
Added a package-lock.json to the repo. Now npm installs the exact versions that worked locally.
Also added a .nvmrc file:
18.19.0
And documented the required Node version in the README.
Future me will thank present me.
The build panicked. I almost did too. Turns out âlatestâ is the enemy of reproducible builds.