Skip to content

fix(runtime): use dynamic import for #content/adapter to prevent prerender failure - #3830

Merged
farnabaz merged 3 commits into
nuxt:mainfrom
gepotumu:fix/lazy-adapter-import-prerender
Aug 26, 2026
Merged

fix(runtime): use dynamic import for #content/adapter to prevent prerender failure#3830
farnabaz merged 3 commits into
nuxt:mainfrom
gepotumu:fix/lazy-adapter-import-prerender

Conversation

@gepotumu

@gepotumu gepotumu commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #3829

When sqliteConnector: 'bun' is configured and the build runs on Node.js (e.g. nuxt build --preset bun), the prerender stage fails with:

ERROR  Only URLs with a scheme in: file, data, and node are supported by the default ESM loader.
Received protocol 'bun:'

Root Cause

database.server.ts uses a static top-level import for #content/adapter:

import adapter from '#content/adapter'   // ← static, resolved at module load time

Node.js ESM loader resolves ALL static imports at module load time, regardless of whether the imported binding is actually called. During prerender, only localAdapter is used (line 18), but the static import forces Node.js to resolve bun:sqlite — which fails because bun: is not a valid Node.js URL scheme.

Fix

Replace the static import with a lazy dynamic import() that is only resolved in the production code path:

let _adapterPromise: Promise<(opts: unknown) => Connector> | undefined

function getAdapter(): Promise<(opts: unknown) => Connector> {
  if (!_adapterPromise) {
    _adapterPromise = import('#content/adapter').then(m => m.default || m)
  }
  return _adapterPromise
}

This makes loadDatabaseAdapter async — a minimal API change since all callers (query.post.ts event handler and _checkAndImportDatabaseIntegrity) already operate in async contexts.

Why This Works

Stage Before After
Module load Static import → Node.js resolves bun:sqliteERROR Only imports localAdapter (Node.js-compatible) → ✅
Prerender runtime Uses localAdapter Uses localAdapter (unchanged)
Production runtime Uses adapter await getAdapter() → dynamic import → works in Bun

Changes

  • src/runtime/internal/database.server.ts — Remove static import of #content/adapter, add lazy getAdapter(), make loadDatabaseAdapter async
  • src/runtime/api/query.post.ts — Await the now-async loadDatabaseAdapter
  • Added regression test and test mock infrastructure

Test Plan

  • Unit test: source does NOT contain static top-level import of #content/adapter
  • Unit test: loadDatabaseAdapter returns a working DatabaseAdapter via dynamic import
  • Unit test: production path uses adapter, adapter is cached across calls
  • Existing 246 unit tests pass
  • Full Nuxt build with content module succeeds (prerender included)

Made with Cursor

@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 033a82ed-be03-4aa4-83e7-2d835c2def9c

📥 Commits

Reviewing files that changed from the base of the PR and between e335c56 and fc911a1.

📒 Files selected for processing (2)
  • src/runtime/internal/database.server.ts
  • test/unit/database.server.prerender.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The runtime now loads the production database adapter through a cached dynamic import. loadDatabaseAdapter is asynchronous, and its callers await the result. Local environments continue using the local adapter. Tests add adapter mocks, manifest data, Vitest aliases, and regression coverage for dynamic loading, caching, concurrent initialization, database methods, and query results.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to fc911

The PR changes adapter loading to avoid resolving Bun-specific modules during Node.js prerendering while preserving the production adapter path; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: using a dynamic import for #content/adapter to prevent prerender failures.
Description check ✅ Passed The description directly explains the bun:sqlite prerender failure, its static-import root cause, the dynamic-import fix, and the related tests.
Linked Issues check ✅ Passed The changes address issue #3829 by avoiding eager bun:sqlite resolution during Node.js prerendering, preserving production adapter loading, updating async callers, and adding regression coverage.
Out of Scope Changes check ✅ Passed The changes remain within scope. The adapter mocks, manifest updates, Vitest aliases, caller update, and regression tests support the database loading fix.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/internal/database.server.ts`:
- Around line 24-30: Update the connector initialization flow around the
module-level db guard and getAdapter so concurrent first calls share a cached
initialization promise, ensuring the adapter factory runs only once and all
callers receive the same connector. Preserve the existing dev/localAdapter and
production adapter selection behavior, and add a Promise.all regression test
covering concurrent initial calls.

In `@test/mock/content-adapter.ts`:
- Line 3: Rename the unused prepare callback parameter from sql to _sql in
test/mock/content-adapter.ts:3-3, test/mock/content-local-adapter.ts:3-3, and
each affected callback in test/unit/database.server.prerender.test.ts:48-48,
57-57, and 87-87, while preserving the mock interface and callback behavior.

In `@test/unit/database.server.prerender.test.ts`:
- Line 75: Replace the `config as any` casts in
`test/unit/database.server.prerender.test.ts` at lines 75, 109, and 113 with one
shared fixture typed as `RuntimeConfig['content']`, and pass that fixture
directly to `loadDatabaseAdapter` at each site.
- Around line 31-42: The static import assertion using staticImportPattern must
reject every top-level `#content/adapter` import form, including named, namespace,
side-effect, and combined imports, while continuing to allow dynamic
import('`#content/adapter`'). Replace the regex-only check with an import parser
or parser-backed assertion that distinguishes static imports from dynamic
imports.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 21366656-2258-4171-b08b-41890e02e463

📥 Commits

Reviewing files that changed from the base of the PR and between dc90e96 and c6c19fb.

📒 Files selected for processing (7)
  • src/runtime/api/query.post.ts
  • src/runtime/internal/database.server.ts
  • test/mock/content-adapter.ts
  • test/mock/content-local-adapter.ts
  • test/mock/content-manifest.ts
  • test/unit/database.server.prerender.test.ts
  • vitest.config.ts

Comment thread src/runtime/internal/database.server.ts Outdated
Comment thread test/mock/content-adapter.ts Outdated
Comment thread test/unit/database.server.prerender.test.ts
Comment thread test/unit/database.server.prerender.test.ts Outdated
@pkg-pr-new

pkg-pr-new Bot commented Aug 10, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@nuxt/content@3830

commit: fc911a1

@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
content Ready Ready Preview Aug 26, 2026 12:09pm

Request Review

@farnabaz
farnabaz force-pushed the fix/lazy-adapter-import-prerender branch 2 times, most recently from 40c7c23 to 944cb8e Compare August 26, 2026 11:58
“finderz” and others added 2 commits August 26, 2026 13:59
…erender failure

When `sqliteConnector: 'bun'` is configured and the build runs on Node.js,
the prerender stage fails because Node.js cannot resolve the `bun:` protocol.

Root cause: `database.server.ts` used a static top-level import for
`#content/adapter`. Node.js ESM loader resolves all static imports at
module load time, regardless of whether the binding is called at runtime.
During prerender only `localAdapter` is used, but the static import of
`adapter` still forces resolution of `bun:sqlite`.

Fix: Replace the static import with a lazy dynamic `import()` that is
only resolved in the production code path (non-prerender, non-dev).
This makes `loadDatabaseAdapter` async, which is a minimal API change
since all callers already operate in async contexts.

Closes nuxt#3829

Co-authored-by: Cursor <cursoragent@cursor.com>
- Prefix unused parameters with `_` to satisfy @typescript-eslint/no-unused-vars
- Replace `as any` casts with a shared typed config fixture
- Remove unused variable assignment

Co-authored-by: Cursor <cursoragent@cursor.com>
@farnabaz
farnabaz force-pushed the fix/lazy-adapter-import-prerender branch from 944cb8e to e335c56 Compare August 26, 2026 12:00

@farnabaz farnabaz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@farnabaz
farnabaz merged commit 400390a into nuxt:main Aug 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqliteConnector: 'bun' fails during prerender when building on Node.js for Bun deployment

2 participants