Skip to content

Conversation

@StirStudios
Copy link

@StirStudios StirStudios commented Dec 10, 2025

Summary

This PR resolves a newly introduced issue where getPage() briefly returned an empty
page object during client-side navigation. This resulted in a visible UI "flash" or
"reset" before the correct page content appeared.

The root cause was:

  • fetchPage() storing new page data after currentPageKey changed.
  • getPage() immediately returning createEmptyPage() whenever a key existed but
    the payload had not yet been populated.

This PR introduces a stable cached page state that eliminates the flicker entirely.


What This PR Changes

1. Adds a stable page cache

A new state value stores the last valid page object:

const lastValidPage = useState<DrupalCePage | null>(
  'drupal-ce-last-valid-page',
  () => null
)

2. Updates fetchPage() to persist new page data

After pageRef.value is populated, we store it as the new valid state:

lastValidPage.value = pageRef.value

3. Rewrites getPage() to never return an empty page

The updated logic:

  • Returns real page data immediately when available.
  • Falls back to lastValidPage instead of createEmptyPage().
  • Removes the router watcher complexity while preserving full functionality.
const getPage = (customKey?: string): Ref<DrupalCePage | null> => {
  const nuxtApp = useNuxtApp()
  const currentKey = useState<string>('drupal-ce-current-page-key', () => '')
  const lastValidPage = useState<DrupalCePage | null>('drupal-ce-last-valid-page', () => null)

  return computed(() => {
    const key = customKey || currentKey.value
    const page = nuxtApp.payload?.data?.[key] as DrupalCePage | undefined

    if (page) {
      lastValidPage.value = page
      return page
    }

    return lastValidPage.value
  })
}

Why This Fix Matters

  • Prevents a jarring UI flash that occurs between navigation and hydration.
  • Ensures layout components (breadcrumbs, titles, meta, admin toolbar) always receive
    consistent page data.
  • Aligns with how Nuxt manages transitional data states.
  • Fixes behavior especially noticeable with Cloudflare caching + CE proxy modes.

No Breaking Changes

  • Does not modify API shape.
  • Does not affect SSR.
  • Does not change existing public methods or signatures.
  • Fully backward compatible with existing Nuxt / Drupal CE integrations.

Additional Notes

  • The fix is minimal and self-contained.
  • Maintains expected behavior even when navigation is throttled, cached, or delayed.
  • Provides a foundation for future improvements such as hydration guards or
    consistent CE slot rendering.

@fago
Copy link
Contributor

fago commented Dec 10, 2025

Thank you for opening this!

I'm not sure I follow why we need to store the last-valid page, when we allready have this:

const currentKey = useState('drupal-ce-current-page-key', () => '')

This should keep referring to the current page, a valid page object, until it changed. Any idea why this is not working in your case?

I feel like this is the underlying bug that we should solve instead of taping band-aid on top. Sure the band-aid works and is a good quick-fix, but for long-term maintainability I think it would be important to fix the underlying bug before it pops-up somewhere else also.

@StirStudios
Copy link
Author

@fago — thanks again for taking the time to dig into this. As requested, here is a clear breakdown of what’s happening, why it happens, why it cannot be fully fixed in userland, and what a true root fix would require inside CE.


The Real Sequence of Events Causing Flicker

In theory, currentKey should always reference a valid page until new data is ready.

In practice, during client-side navigation, the sequence looks like this:

  1. CE updates currentKey to the new route key.
  2. Vue immediately re-renders components that consume getPage().
  3. BUT the new page data has not yet been written to nuxtApp.payload.data[key].
  4. getPage() sees:
    • a new key
    • no payload
    • and returns the empty page placeholder.

This creates a small but visible UI drop:

  • Admin toolbar disappears
  • Breadcrumbs clear
  • Local tasks vanish
  • Layout containers unmount
  • Then everything reappears when data arrives

This is the flicker we are eliminating.


Why This Cannot Be Fully Solved in Userland

The race condition occurs inside the CE update flow, before userland utilities like getPage() have any control.

We do not have a hook that says:

"Only update currentKey once payload.data[key] is actually available."

Because CE controls:

  • when currentKey is set,
  • when payload data is written,
  • and the order in which Vue receives these updates.

So in userland:

  • we can avoid returning empty placeholder pages,
  • but we cannot change the sequence of CE’s updates.

That’s why lastValidPage is the minimal, correct guard.


What a True Root Fix Would Require (Inside CE)

A durable fix must be implemented at the CE layer.

Conceptually, either of these would solve it:

Option A — Delay switching currentKey

Only update currentKey after payload.data[newKey] exists.

Option B — Expose previous page until the new one is fully loaded

Don’t unset the current page state until the next page is ready.

Why this must be fixed inside CE

CE is the only layer that controls:

  • the timing of currentKey updates,
  • population of new payload data,
  • and the order Vue receives those reactive signals.

Userland utilities only read these values — they don’t influence when updates occur.


Why This PR Still Matters (Even After a Future Root Fix)

lastValidPage is:

  • harmless
  • backward-compatible
  • zero API change
  • fully aligned with Nuxt’s useAsyncData behavior (never drop resolved state)

And importantly, it prevents regressions in setups where the flicker is most visible:

  • CE proxy mode
  • Cloudflare caching
  • slower devices or throttled browsers
  • complex layouts relying on getPage()

So even with a future internal CE fix, this fallback cache remains a safe, user-friendly guard.


Summary

  • Yes — the underlying issue is a race condition inside CE.
  • Yes — a deeper fix would require changing how CE sequences currentKey and payload writes.
  • But — until CE changes that, lastValidPage removes the visible regression today and keeps the UI stable.

Happy to explore a CE-level sequencing fix together, but this PR ensures users see no flicker in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants