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.