Deploying Nuxt 4 on Bun
Using Bun as a package manager, running the Nuxt CLI with Bun, building a Bun-targeted server, and starting that server under Bun are four separate decisions. A green build checks only part of the deployment: failures can still appear when the generated server starts or first loads an image adapter.
This is the configuration used for this portfolio, not a claim that Nuxt requires Bun. Verified October 4, 2026: Bun 1.4.2, Nuxt 4.5.2, Nuxt Content 3.16.1, Nuxt Image 2.1.0, and TypeScript 6.0.3. The isolated-output test below ran on Linux ARM64; production builds run on the deployment host.
Make Runtime Selection Explicit
bun install determines how packages are installed; it does not guarantee that a CLI with a Node shebang executes under Bun. Bun's Nuxt guide describes the --bun override. This project puts it inside each relevant script:
{
"packageManager": "[email protected]",
"scripts": {
"dev": "bun --bun run nuxt dev",
"build": "bun --bun run nuxt build",
"typecheck": "bun --bun run nuxt typecheck"
}
}
Consequently, bun run build runs the script that explicitly selects Bun; merely changing the package-manager command in a different project would not have the same effect. The top-level packageManager field records a version expectation, not an installation mechanism.
Some older Bun/Nuxt combinations needed --no-fork to restore the development HMR connection. The original Bun issue was reported against Bun 1.2.5 and closed as completed in July 2026. This repository still documents bun run dev --no-fork as a known development invocation; test HMR on your actual versions rather than treating that flag as a universal Nuxt requirement. It has no role in the production server command.
Match the Nitro Preset to the Server Runtime
The deployed server uses Nitro's Bun preset:
export default defineNuxtConfig({
nitro: {
preset: 'bun',
},
})
Nitro documents the Bun preset and the generated .output/server/index.mjs entry point. The preset changes the generated server; the process that executes the entry point determines the runtime. A Node-targeted output can sometimes run under Bun, but that does not establish that its dependency resolution and lazy-loading paths match the Bun-targeted build.
Run the output, not only the build command:
bun run test
bun run typecheck
bun run build
HOST=127.0.0.1 PORT=3001 bun .output/server/index.mjs
Use an available port and terminate the local process after smoke testing. Building under Bun and subsequently launching with Node is a different compatibility test.
Account for Nuxt Content's SQLite Connector
This site's Nuxt Content 3 database uses SQLite. Its connector documentation states that building under Bun automatically selects bun:sqlite for a Bun target. If CI builds under Node for a Bun deployment, explicitly set content.experimental.sqliteConnector: 'bun'; the build can then use a Node-compatible connector while the deployed runtime uses Bun's driver.
This project builds and runs under Bun, so it does not set that experimental option:
export default defineNuxtConfig({
content: {
database: {
type: 'sqlite',
filename: './.data/content/contents.sqlite',
},
},
nitro: {
preset: 'bun',
},
})
The configured file path is for the content build; it is not a reason to copy the development database into the production artifact. An SQLite native-module error warrants checking the build runtime, selected connector, and deployed runtime before changing package managers or installing another driver. Test the actual .output in isolation: this site's generated server served the article without the source checkout or its node_modules directory.
Exercise an Uncached Image Transformation
Startup is not sufficient when dependencies are loaded dynamically. With the installed Nuxt Image/IPX combination, an earlier generated server started successfully but failed on its first image transformation: IPX loaded srvx/node through a path Nitro had not traced. The project resolves that module from Nuxt Image's own dependency tree and adds it to the Nitro output:
import { createRequire } from 'node:module'
const requireFromImage = createRequire(
import.meta.resolve('@nuxt/image/package.json')
)
const requireFromIpx = createRequire(requireFromImage.resolve('ipx'))
export default defineNuxtConfig({
nitro: {
preset: 'bun',
externals: {
traceInclude: [requireFromIpx.resolve('srvx/node')],
},
},
})
This is a dependency-specific packaging fix, not recommended boilerplate for every Nuxt project. Check whether your resolved IPX release still needs it. Request an image variant not present in prerendered output; an existing cached image cannot exercise the lazy import. In the current Linux ARM64 build, an isolated .output served a new IPX width with HTTP 200. Build on the deployment architecture when bundling Sharp: a locally bundled ARM64 binary is not a portable x86-64 artifact.
Keep Compiler Versions in the Compatibility Set
A runtime update is not a reason to assume the newest compiler is supported by the rest of the toolchain. With vue-tsc 3.3.12, moving this project from TypeScript 6.0.3 to 7.0.2 made type checking fail with Cannot find module 'typescript/lib/tsc'. Returning to the pinned ~6.0.3 passed type checking. Revisit the pin when vue-tsc and Nuxt support the newer compiler; do not present TypeScript 7 as a Bun runtime failure.
Validate the Deployment Artifact
Coolify deploys main with nixpacks.toml. It installs the pinned Bun 1.4.2 into /opt/bun, then uses the same executable for the lockfile install, regression tests, type check, build, and server entry point:
bun install --frozen-lockfile
bun run test
bun run typecheck
bun run build
/opt/bun/bin/bun .output/server/index.mjs
The most useful additional check is to copy only .output into a separate directory and run the entry point there. Probe a published page, a missing route, and a previously ungenerated IPX variant. For this build, those requests returned 200, 404, and 200 respectively. This tests packaging and basic responses, not the delivery of a valid contact submission or every interactive route. Server-side image support must also be rechecked on the actual deployment CPU architecture; successful compilation on a different machine is not enough.
There is no controlled Bun-versus-Node performance benchmark behind this setup. Measure install time, build time, memory, and application latency on the same workload and hardware before attributing an improvement to the runtime.