We currently preload data for several API endpoints so that no requests on pageload are necessary:
core/site storecore/user storecore/modules storemodules/* stores with settingsThere is a problem with that approach because the apiFetch middleware will always have these API requests resolve with this data, even later during the app lifecycle where things might have changed. For example, when you activate a module, core/user authentication data should be refetched. We don't use this anywhere yet so it's not critical, but in the current stage it will not work since it would still not issue an API request and instead rely on the server-preloaded data which at that point will be stale.
We should modify this behavior (e.g. by customizing the behavior of the middleware or potentially implementing our own version of it) so that it only uses the preloaded data for the initial request(s). It should just be one request, but in theory there could be a race condition where the data is requested multiple times. What would probably make sense is to "invalidate" the preloaded data after it is first used, potentially with some timeout of 500ms or so to cater for the race condition (maybe that's not even worth doing).
_Do not alter or remove anything below. The following sections will be managed by moderators only._
apiFetch requests for the initial request(s). For subsequent requests, the data should be invalidated so that the actual API request will be fired.core/modules store should, as part of the setModuleActivation action rely on the core/user store's authentication infrastructure instead of just refetching a current API response.google-site-kit/v1/core/modules/data/list REST endpoint.@wordpress/api-fetch's preloadingMiddleware in assets/js/googlesitekit/api/middleware/preloading.js which exports a custom default createPreloadingMiddleware;assets/js/api-fetch-shim.js, import createPreloadingMiddleware from our internal module, rather than the core non-shim module.GET request, remove that data for future requests before returning it for this initial request.1000 msnext( options ), essentially bypassing its main logicassets/js/googlesitekit/modules/datastore/modules.js:REFETCH_AUTHENTICATION control to be come a registry control, which calls the core/user store's fetchGetAuthentication().REFETCH_AUTHENTICATION into the setModuleActivation action right after the fetchGetModules call in the clause, as it is right now unnecessarily duplicated.Modules class, add google-site-kit/v1/core/modules/data/list to the list of preloaded route paths.apiFetch with a custom middleware to only rely on preloaded data for initial requests on pageload.@felixarntz I think we should also consider that the preloading middleware is unaware of useCache. That is, useCache could be false, but it would still return the response from the preloaded data (if present) if the request was being made for the first time. We should probably ensure the preloaded data is bypassed if a request is made with { useCache: false }. This could be done in siteKitRequest by adding a timestamp query parameter to the URL automatically when useCache is false/disabled. This would bypass the preloading middleware and would also avoid a cached response from the server side as well, without the consumer needing to "know to add the timestamp", it could just be baked into the API client.
Ideally, the preloading would simply prime our cache so the above wouldn't be necessary, but I can't think of a good way to do that due to cache keys, async loading, etc.
Modifying the preloading middleware to be single-use per-endpoint would require that we implement our own version of it, which shouldn't be a problem, but I don't think that addresses all our needs. If we did add the timestamp query param, that would cause the preloaded endpoint to not be invalidated as the URLs wouldn't match exactly. We could check for the URL minus the query param instead.
I'll start an IB based on this; let me know if you agree π
Oh, I like the approach of using timestamps and integrating with our own caching utilities. This approach, including what's mentioned in https://github.com/google/site-kit-wp/issues/1611#issuecomment-638052631, sounds good to me. π
IB β
@aaemnnosttv I like the approach of using a timestamp when useCache is false, and by deleting the cached data upon returning it, we pretty much have the "one-time usage" we want. IB β
Tested a preloaded path returned preloaded data on the first request only, resulting in 1 server request after the first response.


Tested that a request made with a timestamp query parameter deleted/invalidated the cache for a preloaded request causing the same request without the timestamp parameter to hit the server as well.


@felixarntz this has been reverted - assigning to you to move/close.
@aaemnnosttv @tofumatt @ryanwelcher I've amended the ACs to also include the small module-related fixes previously part of #1725 and already implemented there (these changes can mostly be taken from #1732). The original ACs remain pretty much the same, I've just clarified further. I've also added an IB ready for review (the custom preloading middleware setup can reuse code from #1692).
We discussed the issues of the timeout vs having the data store emit an event, but I suppose thinking about itβhow does the data store know when to invalidate things? I suppose it might be nice to invalidate the cache after X amount of time (say 1000ms after the FIRST bit of data is retrieved from the preloaded cache middleware), just so we don't prematurely invalidate the cached data and lose out on the performance wins.
But aside from that this largely sounds good, and I think if we have a better, less fragile solution later we can change this. So it might be nice if the timeout started after the first data load than on something like window.onload or something, but largely the IB here makes sense.
β from me.
I suppose it might be nice to invalidate the cache after X amount of time (say 1000ms after the FIRST bit of data is retrieved from the preloaded cache middleware), just so we don't prematurely invalidate the cached data and lose out on the performance wins.
I had a similar thought as well. Let's initialize the timeout the first time the middleware runs π It should only be set once though, so its timeoutID should be stored and the timeout only initialized if there isn't one.
There are a few related fixes that were in the PR for 1725 that may still be necessary (see https://github.com/google/site-kit-wp/pull/1732).
One key difference with this version of the middleware that I think is important to note is that there is no special handling of a timestamp query parameter, and no changes needed to siteKitRequest so it's a bit simpler in that regard.
I'll amend the IB with the detail of when the timeout is started, but otherwise I think this is good to go. (@felixarntz)
IB β
Edit
Rather than deleting the cache we can simply set a flag that the middleware is invalid which when set we can bypass the normal logic by calling next( options ) immediately. I think this is slightly cleaner and potentially safer than mutating data unnecessarily.
@tofumatt @felixarntz I updated the section about the preloading middleware implementation - assigning back to you for final review.
@aaemnnosttv
When the middleware is invoked for the first time, set a timeout of
1000ms
Just to clarify, you mean invoked as in it's just called, regardless of whether it actually finds preloaded data or not, right? That makes sense, just want to clarify it should not wait until preloaded data is used for the first time, since such a request _may_ just not happen on pageload (although probably in most cases for us it will anyway).
IB β
Just to clarify, you mean invoked as in it's just called, regardless of whether it actually finds preloaded data or not, right?
Yes, correct π
Reviewed the updated IB here and it sounds great. Sounds like we're all in agreement so let's move this to execution π
Moving this out of the release since it's late and this should get some more testing.
QA :heavy_check_mark:. I have tested it and can confirm that apiFetch doesn't rely on preloaded data after initial load and re-fetches it when needed.
Most helpful comment
Reviewed the updated IB here and it sounds great. Sounds like we're all in agreement so let's move this to execution π