Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sharing of HTTP and fetch caches #27

Open
nickcoury opened this issue Feb 23, 2022 · 3 comments
Open

Sharing of HTTP and fetch caches #27

nickcoury opened this issue Feb 23, 2022 · 3 comments

Comments

@nickcoury
Copy link

Overview

Currently, it is possible for standard browser navigations and JavaScript based fetch/XHR calls to hit the same HTTP cache. This may not be true as caches are partitioned further in the future. However, there are advantages to allowing these network requests to hit the same cache, in particular for same-origin applications.

Implementation wise, this can be utilized if the website sends responses in a "polyglot response" format that is both well-formed HTML, and parseable by JavaScript to extract HTML chunks, or extract structured data to utilize with client-side rendering.

Proposal

Formalize circumstances under which the HTTP cache is shared with fetch/XHR for same-origin requests, even as further partitioning occurs. This may be automatic by convention, or may require specific parameters opting in to the behavior for fetch/XHR to utilize the HTTP cache instead of a separate cache. For example, using { mode: 'same-origin' } for fetches.

Use Cases

Use Case 1 - Browser navigation to client-side navigation

Consider a hybrid app that prerenders the initial page and sends complete HTML to the client, then loads subsequent pages using JavaScript, fetch/XHR, and the History API. Each page contains dynamic content, e.g. the results of a search query.

First, the client initiates a client-side navigation to a new page which destroys the initial page content. Next, the user hits the back button to return to the initial page. A new network request must be initiated from JavaScript to fetch the dynamic content from the server.

If the fetch/XHR request can call the initial URL, hit the HTTP cache, and extract the needed dynamic content, it can avoid this network request, server computation, and added latency. This is similar to a fully SSR experience - a back button in this scenario would hit the HTTP cache from disk without an additional network request.

While there are other solutions to this like storing the initial page content in memory or other storage like SessionStorage, these have their own downsides, and also do not help with the reverse situation:

Use Case 2 - Client-side navigation to browser navigation

Consider the same hybrid app. Now, the user navigates client-side one or more times, then clicks a link to an external site. The user then hits the back button to return to the hybrid app, and BFCache misses. In a fully SSR app, the back navigation would again instantly restore the page from disk cache without a network request. In the hybrid case, we will experience a cache miss since the URL was originally fetched via JavaScript, and is now fetched via a browser navigation.

However, if we are using the polyglot response approach and fetched/XHRed the same URL that was pushed to the history stack, it will already be in the HTTP cache and the back navigation will be performed instantaneously without an additional network request.

Even utilizing custom caching in-memory or with other storage, this case can't be solved for browser-based navigations without an additional network request.

Examples

Below are example flows performed in desktop Chrome showing how this works today.

Browser navigation to client-side navigation

  1. In the console, execute await fetch('https://www.google.com/search?q=test+query+1', {mode: 'same-origin', cache: 'only-if-cached'}).
  2. Observe the cache misses.
  3. Navigate to https://www.google.com/search?q=test+query+1
  4. Observe the page loads with type document and has a size, meaning it downloaded from the server.
  5. In the console, execute await fetch('https://www.google.com/search?q=test+query+1', {mode: 'same-origin', cache: 'only-if-cached'}).
  6. Observe the network request of type fetch loaded from (disk cache).

Client-side navigation to browser navigation

  1. Navigate to https://www.google.com/search?q=test+query+1
  2. Navigate to https://www.google.com/search?q=test+query+2
  3. Navigate to https://www.google.com/search?q=test+query+3
  4. Navigate to https://www.google.com/search?q=test+query+3 again
  5. Observe direct navigations never hit (disk cache).
  6. Using browser navigation buttons, go back -> forward.
  7. Observe the browser back/forward buttons hit (disk cache).
  8. Now from https://www.google.com/search?q=test+query+3, right click the refresh button and Empty Cache and Hard Reload.
  9. Again using browser navigation buttons, go back -> forward.
  10. Observe the cache was cleared because the back button missed cache, but forward hit it again.
  11. From https://www.google.com/search?q=test+query+3, right click the refresh button and Empty Cache and Hard Reload again.
  12. From the console, execute await fetch('https://www.google.com/search?q=test+query+2').
  13. Hit the back button.
  14. Observe the browser hits the cache populated by the fetch.
@wanderview
Copy link

What cache-control headers do you set on these resources with dynamic content? Do you allow CDNs to cache them?

@wanderview
Copy link

FWIW, I think you can accomplish what you want with service workers. It gives you the control to handle navigation and subresource caching based on your own policy.

@nickcoury
Copy link
Author

Service workers should be able to handle this, but they have a non-trivial performance cost we're trying to avoid if possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants