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.