Skip to content

db.mjs is rewritten by concurrent non-dev Nuxt builds, silently switching a running dev server onto an empty D1 #924

Description

@lndmanh

Describe the bug

With nitro.preset: 'cloudflare-module' and hub: { db: 'sqlite' }, the generated Drizzle client resolves to a different driver depending on nuxt.options.devlibsql.data/db/sqlite.db in dev, d1 → the miniflare binding otherwise. That choice is baked into a single shared file, node_modules/@nuxthub/db/db.mjs, which nitro dev imports by absolute path (.nuxt/dev/index.mjs contains a literal import { db } from "file:///…/node_modules/@nuxthub/db/db.mjs").

So any second Nuxt instance loaded with dev: false overwrites that file while nuxt dev is running. In my project the writers are vitest (defineVitestProjectloadNuxt({ dev: false })buildNuxt), nuxt prepare from a postinstall, nuxt db generate, and nuxt build.

The dev server keeps the old module in Node's ESM registry until its next module-graph reload — in practice Vite's new dependencies optimized: … — then re-reads the file from disk and starts querying a completely different, empty database. migrations.dev then applies every migration there, so the schema is complete and all queries succeed with zero rows. There is no error and no stack trace; the app just 404s on every authenticated request until the dev server is restarted.

I ended up with two fully-migrated databases (17 rows in _hub_migrations each) and no indication which one was live:

File Driver users rows
.data/db/sqlite.db libsql 2 — the real dev data
.wrangler/state/v3/d1/miniflare-D1DatabaseObject/cfb6….sqlite d1 0 — empty

Caught in the act via mtimes:

21:56:02  .data/db/sqlite.db                 ← dev working, libsql, users = 2
22:05:08  node_modules/@nuxthub/db/db.mjs    ← rewritten to the d1 variant
22:05:08  .data/content/contents.sqlite      ← full buildNuxt (the vitest run)
22:08:26  .nuxt/dev/index.mjs                ← dev reload re-imports db.mjs
22:08:59  .wrangler/state/v3/d1/…/*.sqlite   ← migrations.dev creates 47 tables here

The running server's baked runtimeConfig.hub.db says driver:"libsql" while the db.mjs it imports is the d1 variant — the two disagree in the same tree, and nothing detects it.

Root cause, all references node_modules/@nuxthub/core/dist/module.mjs at 0.10.8:

  1. Mode-dependent artifact in a shared location. setupDatabaseClient writes to a fixed path (:693):

    const physicalDbDir = join(nuxt.options.rootDir, "node_modules", "@nuxthub", "db");
    await writeFile(join(physicalDbDir, "db.mjs"), drizzleOrmContent.replace(...));

    Concurrent Nuxt instances race over it; last writer wins. Nothing keys the path on build mode, buildId, or buildDir.

  2. Nitro dev externalizes it, so disk state wins on reload. There is no invalidation, and no check that the imported client still matches the runtimeConfig.hub.db the bundle was built with.

  3. driver: 'libsql' cannot be pinned to opt out. In resolveDatabaseConfig the cloudflare/!dev branch is evaluated before the explicit-libsql branch (:227-243):

    const userExplicitLibsql = config.driver === "libsql";
    ...
    if (config.driver === "d1") { break; }
    if (hub.hosting.includes("cloudflare") && !nuxt.options.dev) {
      config.driver = "d1";        // ← wins
      break;
    }
    if (userExplicitLibsql) {      // ← unreachable when !dev on cloudflare hosting
      config.connection = defu(config.connection, { url: "" });
      break;
    }

    driver: 'd1' is honoured in every mode; driver: 'libsql' is not.

  4. hosting is preset-derived, so this fires on a laptop (:1073):

    const hosting = process.env.NITRO_PRESET || nuxt.options.nitro.preset || provider;

    Setting nitro.preset: 'cloudflare-module' in nuxt.config.ts, rather than relying on deploy-time env detection, makes hosting cloudflare locally too — which is what activates the dev/non-dev divergence on a dev machine.

Environment

@nuxthub/core 0.10.8
nuxt 4.5.2
nitropack 2.13.4
@nuxt/test-utils 4.1.0
drizzle-orm 0.45.2
@libsql/client 0.17.4
wrangler 4.125.0
vite 8.2.1
Node / npm v26.7.0 / 11.19.0
Config hub: { db: 'sqlite', kv: true, cache: true, blob: true }, nitro.preset: 'cloudflare-module'

Steps to reproduce

  1. Create a Nuxt 4 project with nitro.preset: 'cloudflare-module' and hub: { db: 'sqlite' }, plus a vitest.config.ts using defineVitestProject from @nuxt/test-utils/config.
  2. Run nuxt dev and insert a row (e.g. a user). Confirm grep -c "drizzle-orm/libsql" node_modules/@nuxthub/db/db.mjs returns 1 — dev wrote the libsql client — and that reads work.
  3. Leave the dev server running. In a second terminal run vitest run. It calls loadNuxt({ dev: options.overrides?.dev ?? false }) (@nuxt/test-utils/dist/config.mjs:29) then buildNuxt, so setupDatabaseClient runs with dev: false. grep -c "drizzle-orm/d1" node_modules/@nuxthub/db/db.mjs now returns 1 — the file has been rewritten under the running dev server.
  4. Back in the dev server, trigger anything that makes Vite re-optimize deps and reload the module graph (importing a not-yet-optimized dependency, or touching a file that pulls one in). Watch for new dependencies optimized: ….
  5. Re-request any route that reads the database. Every query now returns zero rows — the row inserted in step 2 is gone, with no error logged. .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite has just been given the full schema by migrations.dev.
  6. Restart nuxt dev. The libsql client is regenerated and the data reappears, which makes the whole thing look like a caching or auth bug rather than a database one.

Expected behavior

A running nuxt dev server should keep talking to the database it started with. A concurrent non-dev Nuxt load — a test run, a postinstall nuxt prepare, nuxt db generate, nuxt build — should not be able to repoint it at a different database, and certainly not silently.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions