Skip to content

fix(preview): fallback to server fetch when WASM query returns empty#3781

Open
surohak wants to merge 4 commits intonuxt:mainfrom
surohak:fix/preview-fallback-to-fetch
Open

fix(preview): fallback to server fetch when WASM query returns empty#3781
surohak wants to merge 4 commits intonuxt:mainfrom
surohak:fix/preview-fallback-to-fetch

Conversation

@surohak
Copy link
Copy Markdown

@surohak surohak commented May 6, 2026

Description

In preview mode (when sessionStorage.previewToken is set), the client exclusively uses the WASM SQLite query. If the local database doesn't have content yet (e.g. a newly created draft that hasn't been synced to the client-side DB), the query returns empty results and the page shows nothing.

This adds a fallback: in preview mode, try the WASM query first; if it returns empty or throws, fall back to fetchQuery which hits the server and can return the draft content.

Outside preview mode, behavior is unchanged — the WASM path is used exclusively as before.

Reproduction

  1. Set up Nuxt Content with Studio preview
  2. Create a new draft document in Studio
  3. Navigate to the draft page before the client WASM DB has synced
  4. Page shows no content (empty)

With this fix, the page correctly falls back to the server query and displays the draft.

Changes

  • src/runtime/client.ts: When previewToken exists in sessionStorage, try WASM query first. If result is empty array or query throws, fall back to fetchQuery.

In preview mode (when `sessionStorage.previewToken` is set), the client
exclusively uses the WASM SQLite query. If the local database doesn't
have content yet (e.g. a newly created draft that hasn't been synced),
the query returns empty results and the page shows nothing.

This adds a fallback: in preview mode, try the WASM query first; if it
returns empty or throws, fall back to `fetchQuery` which hits the server
and can return the draft content.

Outside preview mode, behavior is unchanged.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

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

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

The PR updates executeContentQuery in src/runtime/client.ts to add conditional preview-token handling for client/WebAssembly execution: if sessionStorage.previewToken exists, it first tries queryContentSqlClientWasm and returns non-empty results; on error or empty results it falls back to fetchQuery. If no previewToken is present, the previous WASM-only execution path is retained. No exported/public API signatures were changed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. 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 and specifically summarizes the main change: adding a fallback to server fetch when WASM query returns empty in preview mode.
Description check ✅ Passed The description is directly related to the changeset, explaining the preview mode issue, the solution, reproduction steps, and specific file changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/client.ts`:
- Line 43: Replace the empty catch {} so ESLint no-empty no longer fails while
preserving the existing fallback behavior: in the try/catch around the client
initialization (the catch currently shown as catch {}), add a minimal non-empty
handler such as a single-line comment (e.g. // ignore errors and continue) or a
silent log (e.g. debug or console.debug) that documents the fallback; ensure you
do not change the control flow or rethrow so the current fallback remains
intact.
🪄 Autofix (Beta)

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

Run ID: f9afd454-5614-4e8b-883d-2fdb5d9eb88f

📥 Commits

Reviewing files that changed from the base of the PR and between 55b8793 and 64812db.

📒 Files selected for processing (1)
  • src/runtime/client.ts

Comment thread src/runtime/client.ts Outdated
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

npm i https://pkg.pr.new/@nuxt/content@3781

commit: 6a1ca4a

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/runtime/client.ts (1)

39-39: 💤 Low value

Simplify the non-empty guard — the non-array branch is dead code.

queryContentSqlClientWasm always resolves to Result[], so Array.isArray(result) is invariably true and the : true branch can never execute. The whole expression reduces to result.length > 0.

♻️ Proposed simplification
-        if (result && (Array.isArray(result) ? result.length > 0 : true)) {
+        if (result.length > 0) {
🤖 Prompt for 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.

In `@src/runtime/client.ts` at line 39, The condition "if (result &&
(Array.isArray(result) ? result.length > 0 : true))" is overly defensive because
queryContentSqlClientWasm always returns Result[]; replace it with a simple
non-empty-array check: use "if (result.length > 0)" (or "if (result &&
result.length > 0)" if you prefer extra null-safety) and remove the
Array.isArray ternary and the dead non-array branch; update the check wherever
this pattern appears (referencing the local variable result and the
queryContentSqlClientWasm call) to keep the intent but simplify the logic.
🤖 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/client.ts`:
- Around line 39-46: The current WASM-to-server fallback treats an empty array
result as a missing DB and always calls fetchQuery; change the logic to only
fall back when the WASM query actually failed or returned null/undefined (i.e.,
treat Array.isArray(result) with length===0 as a valid result and return it), or
alternatively gate the fallback with a sync-state flag written/read from
sessionStorage (e.g., a "wasmSynced" boolean set when hydration completes) so
fetchQuery is only invoked when the DB isn't known-synced; update the block
around the Result check and the fetchQuery call accordingly to use one of these
two approaches and keep fetchQuery, result, and the try/catch intact.

---

Nitpick comments:
In `@src/runtime/client.ts`:
- Line 39: The condition "if (result && (Array.isArray(result) ? result.length >
0 : true))" is overly defensive because queryContentSqlClientWasm always returns
Result[]; replace it with a simple non-empty-array check: use "if (result.length
> 0)" (or "if (result && result.length > 0)" if you prefer extra null-safety)
and remove the Array.isArray ternary and the dead non-array branch; update the
check wherever this pattern appears (referencing the local variable result and
the queryContentSqlClientWasm call) to keep the intent but simplify the logic.
🪄 Autofix (Beta)

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

Run ID: 5a0029e5-a64a-46a4-8877-fc881e550e4e

📥 Commits

Reviewing files that changed from the base of the PR and between 64812db and 46e1358.

📒 Files selected for processing (1)
  • src/runtime/client.ts

Comment thread src/runtime/client.ts Outdated
Comment on lines +39 to +46
if (result && (Array.isArray(result) ? result.length > 0 : true)) {
return result as Result[]
}
}
catch {
// WASM query failed — fall through to server fetch
}
return fetchQuery(event, String(collection), sql) as Promise<Result[]>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty-result fallback cannot distinguish "DB not synced" from "genuinely no matches".

When WASM is fully synced and a preview-mode query legitimately returns zero results (e.g., filtering by a tag that matches no content), the condition on line 39 treats it identically to a missing-draft scenario and fires an extra fetchQuery round-trip unconditionally. In an active Studio session this can happen on every navigation to a page with no matching content.

There is no robust client-side signal to distinguish the two cases without additional metadata (e.g., a sync-complete flag written to sessionStorage after the WASM DB is hydrated). Consider documenting this as a known trade-off or adding a sync-state guard if the extra requests become noticeable.

🤖 Prompt for 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.

In `@src/runtime/client.ts` around lines 39 - 46, The current WASM-to-server
fallback treats an empty array result as a missing DB and always calls
fetchQuery; change the logic to only fall back when the WASM query actually
failed or returned null/undefined (i.e., treat Array.isArray(result) with
length===0 as a valid result and return it), or alternatively gate the fallback
with a sync-state flag written/read from sessionStorage (e.g., a "wasmSynced"
boolean set when hydration completes) so fetchQuery is only invoked when the DB
isn't known-synced; update the block around the Result check and the fetchQuery
call accordingly to use one of these two approaches and keep fetchQuery, result,
and the try/catch intact.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/client.ts`:
- Around line 41-42: Read access to sessionStorage can throw in restricted
contexts because it's performed outside the try, so move the sessionStorage read
into the try block and guard it there: inside the function where you currently
check window.sessionStorage?.getItem('previewToken'), wrap the access in try {
const previewToken = window.sessionStorage.getItem('previewToken'); if
(previewToken) { /* existing WASM path using previewToken */ } } catch (e) { /*
allow existing fetchQuery fallback to run */ } ensuring you reference the same
previewToken variable and preserve the existing WASM vs fetchQuery control flow.
🪄 Autofix (Beta)

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

Run ID: 8eb51a42-958e-44c6-96cf-038e2b657933

📥 Commits

Reviewing files that changed from the base of the PR and between 46e1358 and 41709cc.

📒 Files selected for processing (1)
  • src/runtime/client.ts

Comment thread src/runtime/client.ts
Comment on lines +41 to +42
if (window.sessionStorage?.getItem('previewToken')) {
try {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n src/runtime/client.ts | sed -n '34,51p'

Repository: nuxt/content

Length of output: 1053


🌐 Web query:

sessionStorage access throws error restricted browser context private browsing

💡 Result:

Accessing sessionStorage throws errors like "Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document" or SecurityError/QuotaExceededError in restricted browser contexts, primarily due to privacy protections in private/incognito browsing or strict cookie settings.[1][2][3] Key causes: - Private browsing/incognito mode: Browsers restrict storage to prevent tracking. Data is session-only and cleared on close, but access may be limited.[4][5] Modern browsers (Chrome, Firefox, Safari post-2017) generally allow sessionStorage with reduced quotas (~5MB), but older Safari threw QuotaExceededError with 0 quota.[6][7][8] - Third-party/iframe contexts: Cross-origin iframes or embedded scripts (third-party) are blocked in incognito or when "Block third-party cookies" is enabled, especially Chrome.[9][10][11] - User-configured restrictions: Blocking cookies/site data interprets as "prevent persisting data," throwing SecurityError per spec.[1][2] - Other: Opaque origins (file:/data:), sandboxed iframes, enterprise policies.[1] Browser differences (as of 2026): - Chrome/Edge: Works in top-level incognito; fails in third-party iframes/incognito with cookie blocking.[9][10] - Firefox: Generally allows; some tab duplication quirks in private mode.[12] - Safari: Improved since Safari 11; now allows with quota limits in private mode.[6][7] Solution: Always wrap access in try-catch for graceful degradation. Use in-memory fallback. try { sessionStorage.setItem('key', 'value'); } catch (e) { if (e.name === 'SecurityError' || e.name === 'QuotaExceededError') { // Fallback: in-memory storage, cookies, or disable features console.warn('sessionStorage unavailable; using fallback'); // e.g., const fallbackStorage = {}; } } Test availability first: function isStorageAvailable(type = 'sessionStorage') { try { const storage = window[type]; const test = 'storage_test'; storage.setItem(test, test); storage.removeItem(test); return true; } catch (e) { return false; } } For iframes/third-party, request Storage Access API if supported: document.requestStorageAccess({ sessionStorage: true }).[13][14] Users may need to adjust cookie settings for full access, but don't rely on it.[15][10]

Citations:


Guard preview-token reads so fallback still works.

At Line 41, sessionStorage.getItem is evaluated outside the try. In restricted browser contexts (private browsing, third-party iframes, or cookie-blocking settings), this access can throw SecurityError and skip both the WASM path and fetchQuery fallback, causing the function to fail entirely.

Proposed fix
-    if (window.sessionStorage?.getItem('previewToken')) {
+    let hasPreviewToken = false
+    try {
+      hasPreviewToken = !!window.sessionStorage?.getItem('previewToken')
+    }
+    catch {
+      // Ignore storage access errors and continue with non-preview behavior.
+    }
+
+    if (hasPreviewToken) {
       try {
         const result = await queryContentSqlClientWasm<T, Result>(collection, sql)
         if (result && (Array.isArray(result) ? result.length > 0 : true)) {
           return result as Result[]
         }
🤖 Prompt for 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.

In `@src/runtime/client.ts` around lines 41 - 42, Read access to sessionStorage
can throw in restricted contexts because it's performed outside the try, so move
the sessionStorage read into the try block and guard it there: inside the
function where you currently check
window.sessionStorage?.getItem('previewToken'), wrap the access in try { const
previewToken = window.sessionStorage.getItem('previewToken'); if (previewToken)
{ /* existing WASM path using previewToken */ } } catch (e) { /* allow existing
fetchQuery fallback to run */ } ensuring you reference the same previewToken
variable and preserve the existing WASM vs fetchQuery control flow.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/runtime/client.ts (1)

42-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard preview-token access to preserve fallback behavior.

At Line 42, window.sessionStorage?.getItem('previewToken') is outside try. In restricted contexts, that read can throw and prevent both the WASM path and fetchQuery fallback.

Proposed minimal fix
 async function executeContentQuery<T extends keyof Collections, Result = Collections[T]>(event: H3Event | undefined, collection: T, sql: string) {
   if (import.meta.client && window.WebAssembly) {
-    if (window.sessionStorage?.getItem('previewToken')) {
+    let hasPreviewToken = false
+    try {
+      hasPreviewToken = !!window.sessionStorage?.getItem('previewToken')
+    }
+    catch {
+      // Ignore storage access errors and continue with non-preview behavior.
+    }
+
+    if (hasPreviewToken) {
       try {
         const result = await queryContentSqlClientWasm<T, Result>(collection, sql)
         if (result?.length) {
           return result
         }
🤖 Prompt for 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.

In `@src/runtime/client.ts` around lines 42 - 53, The access to
window.sessionStorage?.getItem('previewToken') can throw in restricted
environments and is outside the try, which can prevent both the WASM path
(queryContentSqlClientWasm<T, Result>) and the server fallback
(fetchQuery(event, String(collection), sql)); fix by moving the sessionStorage
read inside the try/catch or by safely guarding it (e.g. check typeof window !==
'undefined' and try/catch the getItem) so any exception falls through to the
existing catch and then to fetchQuery; ensure you still call
queryContentSqlClientWasm only when a token was successfully read and preserve
returning fetchQuery(event, String(collection), sql) as the fallback.
🤖 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.

Duplicate comments:
In `@src/runtime/client.ts`:
- Around line 42-53: The access to
window.sessionStorage?.getItem('previewToken') can throw in restricted
environments and is outside the try, which can prevent both the WASM path
(queryContentSqlClientWasm<T, Result>) and the server fallback
(fetchQuery(event, String(collection), sql)); fix by moving the sessionStorage
read inside the try/catch or by safely guarding it (e.g. check typeof window !==
'undefined' and try/catch the getItem) so any exception falls through to the
existing catch and then to fetchQuery; ensure you still call
queryContentSqlClientWasm only when a token was successfully read and preserve
returning fetchQuery(event, String(collection), sql) as the fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8949b2da-d9ad-48e1-bb7c-a88cc9309e7a

📥 Commits

Reviewing files that changed from the base of the PR and between 41709cc and 6a1ca4a.

📒 Files selected for processing (1)
  • src/runtime/client.ts

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.

1 participant