Skip to content

Split database.compressed.mjs into per-collection chunks for lazy loading in SPA mode #3762

@iBobik

Description

@iBobik

Is your feature request related to a problem? Please describe

In SPA mode (ssr: false), all non-private collections are bundled into a single database.compressed.mjs file that gets downloaded by the browser regardless of which collections are actually needed.

This is especially problematic for i18n/multilingual sites where each locale is a separate collection (e.g., content_en, content_cs, content_sk). A user visiting the Czech version of the site downloads the compressed SQL dumps for ALL languages, even though only the Czech collection will ever be queried. The wasted bandwidth grows linearly with the number of locales and content volume.

This is a broader issue too — any site with multiple collections (e.g., blog, docs, faq) downloads all of them even if the user only navigates to one section.

Describe the solution you'd like

Split database.compressed.mjs into per-collection chunks that are lazy-loaded on first query, similar to how Vite code-splits dynamic imports.

For example, instead of:

// database.compressed.mjs — single monolithic file
export const content_en = "..."
export const content_cs = "..."
export const content_sk = "..."

Generate separate files:

// database.content_en.mjs
export const content_en = "..."

// database.content_cs.mjs
export const content_cs = "..."

And use dynamic import() in the client-side database loader (database.client.js) to fetch only the collection that's being queried:

async function loadCollectionDatabase(collection) {
  const { [collection]: compressedDump } = await import(`./database.${collection}.mjs`)
  // ... decompress and load into SQLite
}

This way, a user on /cs/playground only downloads the Czech content chunk, not all three languages.

Describe alternatives you've considered

  1. private: true on unused collections — This excludes collections from the bundle entirely, but it's a binary on/off. There's no way to say "load this collection lazily on demand." It also requires manual configuration per locale, which doesn't scale.

  2. Separate builds per locale — Build the app once per language with only that locale's collection. This works but doubles/triples build time and deployment complexity, and breaks content sharing across locales.

  3. Accept the tradeoff — For small sites the total compressed size is manageable, but this doesn't scale as content grows or more locales are added.

  4. Use SSR instead of SPA — This avoids the problem entirely since the database stays server-side, but SPA mode is required for certain deployment targets (Capacitor/mobile apps, static hosting without server infrastructure, etc.).

Additional context

  • This was a concern in v1 as well (#802) where db.json could grow very large, but the issue was closed without a solution carried forward to v3.
  • The current runtime already loads collections lazily into SQLite WASM (only on first query), but the compressed data is already in the JS bundle — so the network savings don't apply to the initial download.
  • Related: Issue #2846 reports localized content queries failing in SPA mode, which is a similar pain point at the intersection of i18n + ssr: false.
  • The per-collection HTTP endpoint (/__nuxt_content/{collection}/sql_dump.txt) already exists and fetches collections individually at runtime, but this doesn't help with the initial bundle size since all collections are already embedded in database.compressed.mjs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions