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

feat(nuxt-link): Smart prefetching and $nuxt.isOffline #4574

Merged
merged 32 commits into from Dec 28, 2018
Merged

Conversation

Atinux
Copy link
Member

@Atinux Atinux commented Dec 17, 2018

The idea is to provide the same features as quicklink by default in Nuxt.js.

We use the IntersectionObserver if available, otherwise we document it to explain how to use Polyfill (see nuxt/docs#1068). When a <nuxt-link> is detected within the viewport, we prefetch the linked page to it (thanks to Webpack code-splitting <3).

A no-prefetch prop is also introduced to specify a <nuxt-link> to not automatically prefetch the page (it won't be observed as well).

The usage of destroyed hook let us free the observer as well as when the page is already prefetched, so the performances are really good. 🚀

Like quicklink, we check is the user internet connection is fast enough (and online) to request the splitted-page file, we also keep observing the links so when she keeps navigating and her internet connection change, it could preload the link 😄

It also introduce the way of splitting components for both client & server to optimize bundle size (nuxt-link.client.js and nuxt-link.server.js), this way, the server component does not rely on the IntersectionObserver. When a use disable this smart prefetching feature globally router.prefetchLinks: false, we directly use the nuxt-link.server.js component on the client-bundle to reduce the bundle size 🔥

This PR also introduce $nuxt.isOnline and $nuxt.isOffline so users can directly rely on it for their Vue components (these props are reactive), a little Christmas gift 🎁.

Demo video:
Inuxt-prefetch-demo

@clarkdo
Copy link
Member

clarkdo commented Dec 17, 2018

@galvez galvez added the WIP label Dec 17, 2018
@galvez galvez changed the title [WIP] feat(nuxt-link): Improve <n-link> and add automatic prefetch feat(nuxt-link): Improve <n-link> and add automatic prefetch Dec 17, 2018
@manniL
Copy link
Member

manniL commented Dec 19, 2018

We shouldn't forget that preloading happens for scripts and styles by default. https://github.com/nuxt/nuxt.js/blob/dev/packages/config/src/config/render.js#L6

Co-Authored-By: Atinux <seb@orion.sh>
@Atinux
Copy link
Member Author

Atinux commented Dec 20, 2018

Agree, here we are working only for prefetch (which is disabled by default and should be deprecated for) :)

@codecov-io
Copy link

codecov-io commented Dec 20, 2018

Codecov Report

Merging #4574 into dev will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##              dev    #4574   +/-   ##
=======================================
  Coverage   90.81%   90.81%           
=======================================
  Files          67       67           
  Lines        2220     2220           
  Branches      542      542           
=======================================
  Hits         2016     2016           
  Misses        185      185           
  Partials       19       19
Impacted Files Coverage Δ
packages/vue-app/src/index.js 0% <ø> (ø) ⬆️
packages/config/src/config/router.js 100% <ø> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3515115...23d53fa. Read the comment docs.

@manniL
Copy link
Member

manniL commented Dec 20, 2018

@Atinux But isn't that the "wrong way around"? 🤔

When should you <link rel=”preload”> vs <link rel=”prefetch”>?

Tip: Preload resources you have high-confidence will be used in the current page. Prefetch resources likely to be used for future navigations across multiple navigation boundaries.

(from https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf#89a4)

@manniL
Copy link
Member

manniL commented Dec 20, 2018

Ideally we would preload all linked pages and prefetch the rest (if that's wanted). Or am I mistaken there?

EDIT: On the other hand, preload isn't supported for all major browsers yet (see https://caniuse.com/#search=preload)

@Atinux
Copy link
Member Author

Atinux commented Dec 20, 2018

That's exactly the behaviour of this :)

This is the examples/hello-world/ source code with it:

<!DOCTYPE html>
<html data-n-head-ssr data-n-head="">
  <head data-n-head="">
    <title data-n-head="true">Home page</title>
    <link rel="preload" href="/_nuxt/runtime.js" as="script" />
    <link rel="preload" href="/_nuxt/commons.app.js" as="script" />
    <link rel="preload" href="/_nuxt/vendors.app.js" as="script" />
    <link rel="preload" href="/_nuxt/app.js" as="script" />
    <link rel="preload" href="/_nuxt/pages/index.js" as="script" />
    <style data-vue-ssr-id="d71a54be:0">/* ... */</style>
  </head>
  <body data-n-head="">
    <div data-server-rendered="true" id="__nuxt">
      <div id="__layout">
        <div>
          <h1>Welcome!</h1>
          <a href="/about"> About Page </a>
        </div>
      </div>
    </div>
    <script>
      window.__NUXT__ = {
        layout: 'default',
        data: [{}],
        error: null,
        serverRendered: true
      }
    </script>
    <script src="/_nuxt/runtime.js" defer></script>
    <script src="/_nuxt/pages/index.js" defer></script>
    <script src="/_nuxt/commons.app.js" defer></script>
    <script src="/_nuxt/vendors.app.js" defer></script>
    <script src="/_nuxt/app.js" defer></script>
  </body>
</html>

As you can see, only the preloaded files are the bundle and current page that is rendered.
Then, when the nuxt-link are mounted (after hydration), then we will prefetch the linked pages automatically :)

@manniL
Copy link
Member

manniL commented Dec 20, 2018

Makes total sense 🙈 Sorry! 😄

@Atinux Atinux removed the WIP label Dec 21, 2018
@aldarund
Copy link

aldarund commented Dec 21, 2018

We shouldnt prefetch links when when data saving enabled/2g like in quicklinks.
https://github.com/GoogleChromeLabs/quicklink/blob/c5ed3437ddfd5c28aba5816e57a2e3634f09213a/src/prefetch.mjs#L106

@aldarund
Copy link

Another thing is that not everyone will want this and the only way to disable it is to set noPrefetch to every link. Maybe add option in config for default value for noPrefetch

packages/vue-app/template/client.js Show resolved Hide resolved
packages/vue-app/template/index.js Show resolved Hide resolved
packages/vue-app/template/components/nuxt-link.client.js Outdated Show resolved Hide resolved
packages/vue-app/template/components/nuxt-link.client.js Outdated Show resolved Hide resolved
packages/vue-app/template/components/nuxt-link.client.js Outdated Show resolved Hide resolved
packages/vue-app/template/components/nuxt-link.client.js Outdated Show resolved Hide resolved
packages/vue-app/template/components/nuxt-link.client.js Outdated Show resolved Hide resolved
@pi0
Copy link
Member

pi0 commented Dec 22, 2018

Benefits of the way of this PR over directly prefetching next SSR rendered page:

  • Non-initial page renders are handled by client-side (or at least hydrated). Prefetching SSR content for subsequent pages is likely to introduce more network overheads especially when extractCSS is turned off. If smooth responsiveness during navigation is important, we need to prefetch only needed chunks for related router links. This is done by calling next route matched page chunks.
  • This works for SPA mode too :)

@Atinux
Copy link
Member Author

Atinux commented Dec 28, 2018

Thank you for your comment @addyosmani

You can watch a demo video of this feature:

Inuxt-prefetch-demo

Actually, Nuxt already add the <link rel="preload"> on SSR for critical resources.
When the application is mounted on client-side, we wait for requestIdleCallback, then we check if the linked page is not already prefetched before starting to observe the link.

When the link is intersecting, we check if the user has a decent internet connection (online + 4g for effectiveType + not in data saving mode), then we call Webpack lazy import feature.

Sadly, at the moment, Webpack does not support low priority request for lazy import (cc @sokra @TheLarkInn).
We will work on a solution later on to prefetch the links in the background the service workers are available.

galvez
galvez previously approved these changes Dec 28, 2018
kevinmarrec
kevinmarrec previously approved these changes Dec 28, 2018
benoitemile
benoitemile previously approved these changes Dec 28, 2018
@Atinux Atinux dismissed stale reviews from benoitemile, kevinmarrec, and galvez via 23d53fa December 28, 2018 15:00
@Atinux Atinux merged commit f319033 into dev Dec 28, 2018
@pi0 pi0 deleted the feat-nlink-prefetch branch December 28, 2018 17:53
@tmorehouse
Copy link

Just wondering about action links... i.e. ones that may make state changes on the back end server (ie. disabling a user, deleting an item from a database, etc). Could this inadvertently do things that it shouldn't?

@manniL
Copy link
Member

manniL commented Jan 4, 2019

@tmorehouse These shouldn't be nuxt links 🤔

@tmorehouse
Copy link

@manniL yeah, I know, but there might be a few users who blindly will use <nuxt-link> for all links (as they may want to render a page with a result of the action, and don't do any confirmation checks beforehand, or may have an @click handler and an 'alert' prompt).

@manniL
Copy link
Member

manniL commented Jan 4, 2019

@tmorehouse Even then it shouldn't execute the action as just the js file for the page is loaded and not the page itself

@tmorehouse
Copy link

@manniL Ah yeah, that is true. 😄

daliborgogic pushed a commit to daliborgogic/guess-module that referenced this pull request Jun 23, 2019
According to Atinux's comment nuxt/nuxt#4574 (comment), the "native" prefetch  feature of Nuxt needs to be disabled for Guess.js to work. So I thought it would good to update the Readme accordingly.
@danielroe danielroe added the 2.x label Jan 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet