Using The Interactivity API instead of React (part 1)
When I first built the Veterans Archive, I was very proud of it—it was a whole React app. Years later, I’m wiser, regret my decisions and a better alternative has arrived: the interactivity API + the interactivity router.
You will see references to “i11y” which is my shorthand for “interactivity”
Why rebuild?
Unbeknownst to me at the time, WordPress ships React as the following packages:
- react-dom: 43.9kb (ouch)
- veteranArchive (the code for the page): 6.8kb
- element: 5.7kb
- react: 4.8kb
- url: 4.4kb
- api-fetch: 3.3kb
- regenerator-runtime: 3.0kb
- i18n: 2.9kb
- hooks: 2.3kb
- react-jsx-runtime: 1.2kb
- escape-html: 1.0kb
That’s 11 packages at 79.3kb.
The interactivity + router ships:
- veteranSearch (the code for the page): 1.4kb
- interactivity: 15.8kb
- interactivity router: 12.0kb (loaded when needed)
2 packages at a little over half the javascript. Huge performance win for page loads.
Also, the interactivity api + router gives us a few things for free:
- Mostly javascript-less fallback for those who don’t use it
- Server-side hydration (no initial loading state)
- Less overall code because #1 is handled so gracefully
So, not only do we get a performance win, we also get cleaner, DRY-er code that is also more accessible. It’s a no-brainer from a technical standpoint, so let’s get into the brainy bit.
DRY Code Stats (click if you’re a nerd)
- Total Diff (excluding package-lock.json): +642 | -1601
- That’s 959 lines removed, and (maybe more importantly) a whole directory removed (
veteran-archive)- This included 4 subdirectories and 15 files in favor of a much simpler 3 files of Javascript.
Loosely based on the Choctaw Small Business Calendar, here’s how I rebuilt the search app with the interactivity API.
Summary of Performance Gains
The Process
- Page tested: /veterans (archive & search page)
- Env: Incognito, no optimize plugins, PHP 8.4
- Disable Cache: ✅
Getting Started
- Navigate to page
- reload once to warm cache, then run tests:
The Tests
- Lighthouse (Desktop performance)
- Page Load x5
- Pagination 3 & 4, reset to main
- Legion of Merit
- Chad
- Stephen
- Lighthouse (Desktop Performance)
The Results
| Metric | Staging | Development | Difference |
|---|---|---|---|
| Lighthouse Desktop | 55, 59 (avg 57) | 94, 96 (avg 95) | +38 points |
| Avg page load finish | 1.06s | 746ms | ~30% faster |
| Page 3 fetch | 6.11s | 593ms | ~90% faster |
| Page 4 fetch | 5.44s | 4.42s | ~19% faster |
| “Legion of Merit” search | 6.14s | 391ms | ~94% faster |
| “Chad” search | 8.52s | 2.86s | ~66% faster |
| “Stephen” search | 6.60s | 2.60s | ~61% faster |
| JS requests | 19 | 6 | ~68% fewer |
| JS transferred | 866kb | 36.1kb | ~96% smaller |
| JS resources | 2,057kb | 99.2kb | ~95% smaller |
The Assets
To enqueue the assets properly, a few things need to be set up:
npm run start/buildneeds to be using the--experimental-modulesflag,- Webpack should be configured to handle module and classic builds concurrently (see the starter-theme for example), and
- The theme needs to properly enqueue the module script.
Let’s start with #2.
In the updatedConfig.entry object, drop in your script:
const updatedConfig = {
...webpackConfig,
entry: {
...webpackConfig.entry,
veteranSearch: path.resolve(
THEME_SRC,
'js/veteran-search/view.ts'
),
},
output: {
...webpackConfig.output,
path: THEME_DIST,
filename: '[name].js',
},
};Pretty simple, but if you don’t do this, Query Monitor should complain that you’re doing it wrong because the dependency array will build out wp-interactivity and not @wordpress/interactivity, type=>module like it expects.
#3, the enqueue
Similarly, it’s a pretty minor tweak, but instead of wp_enqueue_script, you’ll use wp_enqueue_script_module and wp_interactivity()->add_client_navigation_support_to_script_module()in the wp_enqueue_scripts callback function.
<?php
if ( is_post_type_archive( 'veterans' ) ) {
$veteran_search_assets = require get_template_directory() . '/dist/veteranSearch.asset.php';
wp_enqueue_script_module('veteran-search', get_template_directory_uri() . '/dist/veteranSearch.js', $veteran_search_assets['dependencies'], $veteran_search_assets['version']);
wp_interactivity()->add_client_navigation_support_to_script_module('veteran-search');
}Process the interactivity directives
In a classic theme like Veterans Archive and CSBD, WordPress needs some way to process the directives and handle server-side render + client-side hydration. Since the search app is basically the whole archive template, it’s pretty easy using the process_directives function:
<?php
/**
* Archive: Veterans
*
* @package ChoctawNation
*/
use ChoctawNation\VeteranSearch\Interactivity_Helper;
get_header();
$i11y = new Interactivity_Helper();
ob_start();
?>
<div data-wp-interactive="<?php echo $i11y::STORE; ?>" data-wp-router-region="<?php echo $i11y::ROUTER_REGION; ?>">
<!-- rest of page... -->
</div>
<?php
$html = ob_get_clean();
echo wp_interactivity_process_directives( $html );
get_footer();The main thing here is the ob_start() + echo wp_interactivity_process_directives($html) pattern. Of note, I’ve also got a helper class ($i11y) that stores the keys for the data-wp-interactive and data-wp-router-region values.