fix(preview): fallback to server fetch when WASM query returns empty#3781
fix(preview): fallback to server fetch when WASM query returns empty#3781
Conversation
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.
|
Someone is attempting to deploy a commit to the Nuxt Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR updates Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
src/runtime/client.ts
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/runtime/client.ts (1)
39-39: 💤 Low valueSimplify the non-empty guard — the non-array branch is dead code.
queryContentSqlClientWasmalways resolves toResult[], soArray.isArray(result)is invariablytrueand the: truebranch can never execute. The whole expression reduces toresult.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
📒 Files selected for processing (1)
src/runtime/client.ts
| 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[]> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
src/runtime/client.ts
| if (window.sessionStorage?.getItem('previewToken')) { | ||
| try { |
There was a problem hiding this comment.
🧩 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:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/sessionStorage
- 2: https://html.spec.whatwg.org/webstorage.html
- 3: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
- 4: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
- 5: http://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria
- 6: https://stackoverflow.com/questions/18860098/on-a-browser-sessionstorage-in-safaris-private-browsing-does-not-work-the-same
- 7: https://docs.bswen.com/blog/2026-03-22-safari-localstorage-private-browsing
- 8: https://www.grizzlypeaksoftware.com/library/localstorage-and-sessionstorage-strategies-iwz8t0vi
- 9: https://stackoverflow.com/questions/64123691/chrome-incognito-third-party-web-storage
- 10: https://stackoverflow.com/questions/63195318/domexception-failed-to-read-the-localstorage-property-from-window-in-chrom
- 11: Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document. auth0/auth0-spa-js#593
- 12: https://stackoverflow.com/questions/51043286/unable-to-access-localstorage-in-second-tab-when-opened-in-incognito-mozilla-fi
- 13: https://developer.mozilla.org/en-US/docs/Web/API/StorageAccessHandle/sessionStorage
- 14: http://developer.mozilla.org/en-US/docs/Web/API/StorageAccessHandle/sessionStorage
- 15: https://stackoverflow.com/questions/58152236/what-to-do-when-sessionstorage-access-denied-due-to-browser-settings
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.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/runtime/client.ts (1)
42-53:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard preview-token access to preserve fallback behavior.
At Line 42,
window.sessionStorage?.getItem('previewToken')is outsidetry. In restricted contexts, that read can throw and prevent both the WASM path andfetchQueryfallback.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
📒 Files selected for processing (1)
src/runtime/client.ts
Description
In preview mode (when
sessionStorage.previewTokenis 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
fetchQuerywhich hits the server and can return the draft content.Outside preview mode, behavior is unchanged — the WASM path is used exclusively as before.
Reproduction
With this fix, the page correctly falls back to the server query and displays the draft.
Changes
src/runtime/client.ts: WhenpreviewTokenexists in sessionStorage, try WASM query first. If result is empty array or query throws, fall back tofetchQuery.