Uh oh!
There was an error while loading. Please reload this page.
fix(preview): fallback to server fetch when WASM query returns empty - #3781
fix(preview): fallback to server fetch when WASM query returns empty#3781surohak wants to merge 4 commits into
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
Uh oh!
There was an error while loading. Please reload this page.
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
| 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
| 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.
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.