Interactivity API’s client navigation improvements in WordPress 6.9

In WordPress 6.9, the client-side navigation feature provided by the @wordpress/interactivity-router module has been expanded to cover additional use cases that were previously unsupported.

Support for new script modules and stylesheets

Previously, only the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. of the new page was updated, keeping the styles present in the initial page and ignoring any new script modules. This worked for basic client-side navigation cases, but it didn’t handle more complex situations, such as when new blocks appear on the next page.

With this update, WordPress now replaces stylesheets and loads any script modules after client-side navigation.

  • The new algorithm reuses the stylesheets shared with the previous page to minimize networknetwork (versus site, blog) requests, loads any new stylesheet not present in the previous navigations, and disables those that no longer apply.
  • The new algorithm also loads all the script modules belonging to interactive blocks that didn’t exist on the previous pages. To correctly support module dependencies, new importmap definitions are also supported.
  • To maintain the experience of instant navigations, prefetching a page also prefetches all the stylesheets and script modules that were not previously prefetched or loaded.

While all styles are handled in the browser by the Interactivity APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways. runtime, script modules must be explicitly marked as compatible with client-side navigation.

For blocks, this is done automatically by checking the supports.interactivity to either true or contain an object with { clientNavigation: true } to mark its support in their block.json file (see docs here). If that’s the case, the corresponding viewScriptModule will be automatically marked as compatible.

For other script modules registered “manually”, developers must use the add_client_navigation_support_to_script_module method of the main WP_Interactivity_API instance, as shown in the example below.

wp_register_script_module( 'my-module', '/my-module.js' );
wp_interactivity()->add_client_navigation_support_to_script_module( 'my-module' );
wp_enqueue_script_module( 'my-module' );

That method would mark the passed script module ID as compatible, and its script tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) would include a data-wp-router-options directive containing the value {"loadOnClientNavigation":true}.

Script modules without that directive are ignored and not imported. Remember that regular scripts are simply ignored regardless of whether they contain the data-wp-router-options directive, as they are not supported by the Interactivity API.

For details on the implementation, see #70353.

Support for router regions inside interactive elements

Router regions are those elements marked with the data-wp-router-region directive. When the navigate() action from @wordpress/interactivity-router is invoked, the content of these regions is updated to match the newly requested page.

In previous WordPress versions, router regions needed to match a root interactive element (i.e., one of the top-most elements with a data-wp-interactive directive). This meant that if the data-wp-router-region directive was used anywhere else in an interactive region, its content wouldn’t be updated.

<div data-wp-interactive="example">
    <button data-wp-on--click="actions.doSomething">Click me!</button>
    <div
      data-wp-interactive="example"
      data-wp-router-region='example/region-1'
    >
      I wasn't updated on client navigation (now I am!)
    </div>
</div>

Now, router regions are updated whenever they are placed in an interactive region. The only requirement is that they must still be used alongside the data-wp-interactive directive so they receive the corresponding namespace.

For more details, refer to the related issue #71519 and pull request #71635.

New attachTo option for router regions

In WordPress 6.9, router regions accept an attachTo property that can be defined inside the data-wp-router-region directive, allowing the region to be rendered even when it was missing on the initial page. This option supports cases like overlays that are necessary for a blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience., but may appear outside all the regions.

The attachTo property should be a valid CSSCSS Cascading Style Sheets. selector that points to the parent element where the new router region should be rendered. For example, the following router region would be rendered in <body> if it appears on a visited page, even if it wasn’t initially rendered.

<div
  data-wp-interactive="example"
  data-wp-router-region='{ "id": "example/region", "attachTo": "body" }'
>
  I'm in a new region!
</div>

See #70421 for more details.

Improved getServerState and getServerContext functions

When using the Interactivity API with client-side navigation, the getServerState() and getServerContext() functions now properly handle the following scenarios:

  1. Properties that are modified on the client but should reset to server values on navigation
    Now, whenever getServerState or getServerContext is tracking a value that doesn’t change after a client-side navigation, it will still trigger an invalidation so that it can be used to reset values, such as:
   const { state } = store( 'myPlugin', {
    // ...
    callbacks: {
        resetCounter() {
            const serverState = getServerState(); // Always { counter: 0 };
            state.counter = serverState.counter; // Reset to 0;
        },
    },
   } );
  1. Properties that only exist on certain pages
    Server state and contexts are now fully overwritten: only the properties present on the current page are retained, and those from previous pages are removed. This allows having the certainty of knowing if a property doesn’t exist in the server state, even if it was present on the previous page.
   store( 'myPlugin', {
    // ...
    callbacks: {
        onlyWhenSomethingExists() {
            const serverState = getServerState();
            if ( serverState.something ) {
                // Do something...
            }
        },
    },
   } );

Additionally, these functions now include proper type definitions and error messages in debug mode, among other improvements.

See #72381 for more details.

Props to @darerodz and @luisherranz for the implementations.

#6-9, #dev-notes, #dev-notes-6-9, #interactivity-api