From 5fbcee7f5649b342a90d510dc2b3aba4179099d6 Mon Sep 17 00:00:00 2001 From: Glyn Gray Date: Tue, 9 Dec 2025 18:49:49 -0800 Subject: [PATCH 1/2] fix: avoid transient empty page state by caching last valid page in getPage() --- src/runtime/composables/useDrupalCe/index.ts | 60 +++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/runtime/composables/useDrupalCe/index.ts b/src/runtime/composables/useDrupalCe/index.ts index 749972e..31323b9 100644 --- a/src/runtime/composables/useDrupalCe/index.ts +++ b/src/runtime/composables/useDrupalCe/index.ts @@ -12,6 +12,11 @@ export const useDrupalCe = () => { const config = useRuntimeConfig().public.drupalCe const privateConfig = import.meta.server && useRuntimeConfig().drupalCe + // Stores the most recently valid, complete page. + // Used by getPage() to avoid UI flicker when the new payload + // key is not yet available during route navigation. + const lastValidPage = useState('drupal-ce-last-valid-page', () => null) + /** * Returns an empty page structure with default values */ @@ -266,6 +271,9 @@ export const useDrupalCe = () => { // Store the current page key for getPage() lookup currentPageKey.value = useFetchOptions.key + // Persist the full valid page to avoid UI flicker on next navigation + lastValidPage.value = pageRef.value + return pageRef } @@ -335,46 +343,30 @@ export const useDrupalCe = () => { * * @param customKey Optional custom cache key. If not provided, uses current route's cache key. */ - const getPage = (customKey?: string): Ref => { + const getPage = (customKey?: string): Ref => { const nuxtApp = useNuxtApp() - const currentPageKey = useState('drupal-ce-current-page-key', () => '') - - // Set up route watcher to keep currentPageKey in sync (for KeepAlive scenarios) - // Only needed when using default key (not custom key) - if (!customKey && import.meta.client) { - const watcherInitialized = useState('drupal-ce-watcher-init', () => false) - if (!watcherInitialized.value) { - watcherInitialized.value = true - try { - const router = useRouter() + // Stores the key used to look up the page in nuxtApp.payload.data + const currentKey = useState('drupal-ce-current-page-key', () => '') - // Determine proxy mode based on config (same logic as fetchPage) - const skipProxy = !config.serverApiProxy + // Stores the last successfully loaded full page + const lastValidPage = useState( + 'drupal-ce-last-valid-page', + () => null + ) - // Update key on initial load - currentPageKey.value = computePageKey(skipProxy, nuxtApp) + return computed(() => { + const key = customKey || currentKey.value + const page = nuxtApp.payload?.data?.[key] as DrupalCePage | undefined - // Use router.afterEach to ensure navigation is fully complete before updating - router.afterEach(() => { - currentPageKey.value = computePageKey(skipProxy, nuxtApp) - }) - } - catch { - // Silently skip if not in proper Nuxt context (e.g., unit tests) - } + // If we have a real page for this key, update stable cache + if (page) { + lastValidPage.value = page + return page } - } - // Return computed ref that looks up the page data in the reactive Nuxt payload - // Uses custom key if provided, otherwise uses current route's key - return computed(() => { - const key = customKey || currentPageKey.value - if (key && nuxtApp.payload.data[key]) { - return nuxtApp.payload.data[key] - } - // Return empty page data if no page has been fetched yet - return createEmptyPage() + // Otherwise return the last good page (avoids flicker) + return lastValidPage.value }) } @@ -601,7 +593,7 @@ export const useDrupalCe = () => { getMenuBaseUrl, getPageLayout, usePageHead, - + } } From 56b38ff51ae379a396e1af823fc8b03476a2ff93 Mon Sep 17 00:00:00 2001 From: Glyn Gray Date: Wed, 17 Dec 2025 09:02:02 -0800 Subject: [PATCH 2/2] fix: update current page key only after page data is available --- src/runtime/composables/useDrupalCe/index.ts | 63 ++++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/runtime/composables/useDrupalCe/index.ts b/src/runtime/composables/useDrupalCe/index.ts index 31323b9..d499db1 100644 --- a/src/runtime/composables/useDrupalCe/index.ts +++ b/src/runtime/composables/useDrupalCe/index.ts @@ -12,11 +12,6 @@ export const useDrupalCe = () => { const config = useRuntimeConfig().public.drupalCe const privateConfig = import.meta.server && useRuntimeConfig().drupalCe - // Stores the most recently valid, complete page. - // Used by getPage() to avoid UI flicker when the new payload - // key is not yet available during route navigation. - const lastValidPage = useState('drupal-ce-last-valid-page', () => null) - /** * Returns an empty page structure with default values */ @@ -269,10 +264,10 @@ export const useDrupalCe = () => { pageRef.value.key = useFetchOptions.key // Store the current page key for getPage() lookup - currentPageKey.value = useFetchOptions.key - - // Persist the full valid page to avoid UI flicker on next navigation - lastValidPage.value = pageRef.value + // Only update once page data exists to avoid transient empty-page states + if (pageRef.value) { + currentPageKey.value = useFetchOptions.key + } return pageRef } @@ -343,30 +338,46 @@ export const useDrupalCe = () => { * * @param customKey Optional custom cache key. If not provided, uses current route's cache key. */ - const getPage = (customKey?: string): Ref => { + const getPage = (customKey?: string): Ref => { const nuxtApp = useNuxtApp() + const currentPageKey = useState('drupal-ce-current-page-key', () => '') - // Stores the key used to look up the page in nuxtApp.payload.data - const currentKey = useState('drupal-ce-current-page-key', () => '') + // Set up route watcher to keep currentPageKey in sync (for KeepAlive scenarios) + // Only needed when using default key (not custom key) + if (!customKey && import.meta.client) { + const watcherInitialized = useState('drupal-ce-watcher-init', () => false) - // Stores the last successfully loaded full page - const lastValidPage = useState( - 'drupal-ce-last-valid-page', - () => null - ) + if (!watcherInitialized.value) { + watcherInitialized.value = true + try { + const router = useRouter() - return computed(() => { - const key = customKey || currentKey.value - const page = nuxtApp.payload?.data?.[key] as DrupalCePage | undefined + // Determine proxy mode based on config (same logic as fetchPage) + const skipProxy = !config.serverApiProxy + + // Update key on initial load + currentPageKey.value = computePageKey(skipProxy, nuxtApp) - // If we have a real page for this key, update stable cache - if (page) { - lastValidPage.value = page - return page + // Use router.afterEach to ensure navigation is fully complete before updating + router.afterEach(() => { + currentPageKey.value = computePageKey(skipProxy, nuxtApp) + }) + } + catch { + // Silently skip if not in proper Nuxt context (e.g., unit tests) + } } + } - // Otherwise return the last good page (avoids flicker) - return lastValidPage.value + // Return computed ref that looks up the page data in the reactive Nuxt payload + // Uses custom key if provided, otherwise uses current route's key + return computed(() => { + const key = customKey || currentPageKey.value + if (key && nuxtApp.payload.data[key]) { + return nuxtApp.payload.data[key] + } + // Return empty page data if no page has been fetched yet + return createEmptyPage() }) }