Next.js: Allow bypass "getData" of page component on client side navigation

Created on 15 Apr 2020  路  24Comments  路  Source: vercel/next.js

Feature request

Is your feature request related to a problem? Please describe.

@aralroca shared some useful insights on how he archived his app performance.

Using SPA navigation (clicking a Link or Router.push) the page is rendered after getInitialProps or getServerSideProps is resolved.

However, with getInitialProps you can "skip" fetching the data to don't block the navigation. So you can render the new page with skeletons just at the same moment that you clicked the Link.

With this, I'm using getInitialProps only for real SSR and not for client navigation. Doing the fetch navigation on a useEffect.

But getServerSideProps is not possible (or at least I dunno how to handle this) to do the same behavior.

He still relies on getInitialProps instead getServerSideProps because it allows him to skip client-side data fetching on route change. He loads the data on the next useEffect(..., []) lifecycle. SSR is not affected. In that scenario, he can ship the app skeleton faster to the user and load the data afterward on the client.

This sounds reasonable when your static content dominate the page which is often the case.

Describe the solution you'd like

export const config = {
   clientDataFetch: false
}

export async function getServerSideProps(){
  return {
    props: await fetchProps()
  }
}

Working PR which demonstrates the behavior by defining skipDataFetch on Link component. https://github.com/StarpTech/next.js/tree/add/skip_data_fetch

Additional context

Most helpful comment

has there been any progress on this? Im using getServerSideProps for seo reasons to provide the initial data for some pages, then caching it (using apollo). However when navigating back to the same page (data is already cached) its re-running the getServerSideProps and blocking the navigation until that resolves.

The data fetching in the getServerSideProps isn't super slow, but the process of calling the getServerSideProps is very slow and this leads to a very slow navigation to pages that contain getServerSideProps.

Am I missing something here?

Thanks

All 24 comments

IMO this will be very useful but I would add this behavior in the getServerSideProps instead of adding the behavior on the Link or Router.push.

My proposal:

export async function getServerSideProps(){
 return {
    props: await fetchProps(),
    navigation: false,
 }
}

I dunno how feasible is to implement this. But doing in this way it would be very useful.

@aralroca the way that you're suggesting it would require getServerSideProps to be executed before knowing it shouldn't execute. Which makes it not feasible for what the feature request wants to achieve.

Removed the first contentful paint mention as it's incorrect, this feature wouldn't make FCP faster for initial load which is the only thing that eg Google tracks.

@timneutkens I think it's very accurate

First Contentful Paint (FCP) measures the time from navigation to the time when the browser renders the first bit of content from the DOM. This is an important milestone for users because it provides feedback that the page is actually loading.

https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint

Overall putting it on the <Link> is tricky considering you'd have to add it to every single link to that specific page. On the page level you know if it's implementing client-side fetches. So the solution might be adding a property to export const config = {} similar to how we handle AMP support. One problem that comes up is that we eagerly prefetch the data for getStaticProps. Even with getServerSideProps we eagerly fetch the JSON when a link is clicked, so we don't wait on executing the JS for the page before fetching the data. One way to work around this would involve adding that marker into the buildManifest, but that increases the initial JS size of all pages.

@timneutkens I think it's very accurate

First Contentful Paint (FCP) measures the time from navigation to the time when the browser renders the first bit of content from the DOM. This is an important milestone for users because it provides feedback that the page is actually loading.

https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint

It's tricky to define it as such as FCP measures the time from navigation of a full browser navigation. In case of client-side navigation you already have rendered DOM and only bits are replaced in the UI (eg header/footer could persist).

@aralroca the way that you're suggesting it would require getServerSideProps to be executed before knowing it shouldn't execute. Which makes it not feasible for what the feature request wants to achieve.

Yes. That's true... However, it will be useful to do it on the page. Probably outside of getServerSideProps. Something like:

export const config = {
   getServerSideProps: { navigation: false }
}

export async function getServerSideProps(){
  return {
    props: await fetchProps()
  }
}

Overall putting it on the is tricky considering you'd have to add it to every single link to that specific page. On the page level you know if it's implementing client-side fetches. So the solution might be adding a property to export const config = {} similar to how we handle AMP support. One problem that comes up is that we eagerly prefetch the data for getStaticProps. Even with getServerSideProps we eagerly fetch the JSON when a link is clicked, so we don't wait on executing the JS for the page before fetching the data. One way to work around this would involve adding that marker into the buildManifest, but that increases the initial JS size of all pages.

I agree, adding it to <Link> will probably lead to mistakes in big applications. But this is a general problem in next.js. Every Link must contain the correct prefetch, as, query, passHref parameter or a wrong href when using dynamic routes will lead to a silent page reload https://github.com/zeit/next.js/issues/11660. Errors you will recognize much later.

I think for the short path we could go with an additional parameter.

Overall putting it on the is tricky considering you'd have to add it to every single link to that specific page. On the page level you know if it's implementing client-side fetches. So the solution might be adding a property to export const config = {} similar to how we handle AMP support. One problem that comes up is that we eagerly prefetch the data for getStaticProps. Even with getServerSideProps we eagerly fetch the JSON when a link is clicked, so we don't wait on executing the JS for the page before fetching the data. One way to work around this would involve adding that marker into the buildManifest, but that increases the initial JS size of all pages.

I agree, adding it to <Link> will probably lead to mistakes in big applications. But this is a general problem in next.js. Every Link must contain the correct prefetch, as, query, passHref parameter or a wrong href when using dynamic routes will lead to a silent page reload #11660. Errors you will recognize much later.

I think for the short path we could go with an additional parameter.

But I imagine that when you have some skeletons / spinners + useEffect to fetch the data on client-side on some page is because you want always this behavior on the page. Doing on the Link / Router you always need to remember what pages have this behavior. I think is more error-prone. If you are the owner of the page maybe it's clear for you, but not always is the case.

Doing on the Link / Router you always need to remember what pages have this behavior. I think is more error-prone. If you are the owner of the page maybe it's clear for you, but not always is the case.

I 100% agree, but as I said before the responsibility lie already in <Link> usage. For the long term, I'd like to manage it on a page basis.

I think for the short path we could go with an additional parameter.

It is not feasible to add short term solutions for something as popular as Next. If they add a feature, they'd probably have to support it for years.

Agree with @aralroca, I don't think fixing this at the <Link/> level is the right approach. It would be very error prone as there could potentially be multiple <Link />'s scattered across multiple components pointing to the page.

That said, I'd still like to see a way to control fetching of data on a client side page navigation. Especially, with useSWR, where I'd like some initialData via SSR on the first page load, but going forward, I'd like the client side to essentially "take over" control from that point forward.

It is not feasible to add short term solutions for something as popular as Next. If they add a feature, they'd probably have to support it for years.

I agree but It's better to try out things, collect feedback rather than waiting weeks or months for an RFC. For that, we can use "experimental" flags.

@timneutkens How much effort is needed to implement it in the AMP like way?

+1 for config to skip getServerSideProps on client. It would be useful also when you want to do PWA that starts immediately from service worker response, and let components to fetch their data and show some loading states. While still keeping SSR for users that are visiting for first time or not have active service worker.

I just tried Next for the first time last week and was amazed to discover this wasn't the way it worked.

The behaviour (default or via an option) should be:

On the first visit to the Next app it server side renders
THEN
every other page is client side rendered

The speed and SEO benefit of SSR only applies on initial load of the app when you have extra round trips (url -> server -> client -> API -> client).

Once the initial application is loaded, then when you click a new page a client-side app becomes (Client -> API -> Client) which is exactly the same trips as (Client -> Server -> Client). But much faster client side because the server doesn't have to do the heavy lifting of rendering the page AND if you already have the data you can display the page instantly.

I really hope I am just missing setting in Next, because this is a pretty fundamental idea of SSR and App hydration, and something I've been doing for 4+ years with React apps.

Basic App:

People List page, loads all the people and some data about the,
Person page, loads all the data about a single person.

If I visit the people page, it should server side render the page, and include the all the people.
When I click a person, I should instantly client-side render the person page and hit the API for any missing data.

If I visit the person page, it should server side render the page, with all the data about the one person.
When I click a link back to the people page, I should client side render the page and make an API req for all the clients.

Now its fast for /people/ and /people/id via server side rendering. Instant for /people/ -> /people/id. And the same as SSR load time for /people/id -> /people/

I think to solve this issue, we need one more getClientSideProps method as well to preserve SPA navigation behaviour.

I just tried Next for the first time last week and was amazed to discover this wasn't the way it worked.

The behaviour (default or via an option) should be:

On the first visit to the Next app it server side renders
THEN
every other page is client side rendered

The speed and SEO benefit of SSR only applies on initial load of the app when you have extra round trips (url -> server -> client -> API -> client).

Once the initial application is loaded, then when you click a new page a client-side app becomes (Client -> API -> Client) which is exactly the same trips as (Client -> Server -> Client). But much faster client side because the server doesn't have to do the heavy lifting of rendering the page AND if you already have the data you can display the page instantly.

I really hope I am just missing setting in Next, because this is a pretty fundamental idea of SSR and App hydration, and something I've been doing for 4+ years with React apps.

Basic App:

People List page, loads all the people and some data about the,
Person page, loads all the data about a single person.

If I visit the people page, it should server side render the page, and include the all the people.
When I click a person, I should instantly client-side render the person page and hit the API for any missing data.

If I visit the person page, it should server side render the page, with all the data about the one person.
When I click a link back to the people page, I should client side render the page and make an API req for all the clients.

Now its fast for /people/ and /people/id via server-side rendering. Instant for /people/ -> /people/id. And the same as SSR load time for /people/id -> /people/

I just wanted to mention that I come from gatsby and I tried next.js a week ago and I had assumed this behavior as well. Plus it seems that grapqhl packages like next-apollo or next-urql all disable Automatic Static Optimisation. (Like what?). Shouldn't it be the default behavior for all "non-nextjs" API calls to be from the client? The other 2 options should be:

  1. The API output is inlined into server-rendered (rendered only once) which I believe is done through getStaticProps but it seems libraries don't support just yet;
  2. The API is called every time from the server, server render takes place, JSON passed to the client (I might be wrong with my understanding of next.js internals, please correct me)
    Seems like the worst of both worlds is the default option.

I think to solve this issue, we need one more getClientSideProps method as well to preserve SPA navigation behavior.

I am entirely sold on this. It would have the added benefit of making newbies, like me, keep in mind what really is happening behind the scenes. Abstracting out way too many bites in the ass when you get a peculiar bug. And in fact, with impatient people who get stuck behind such a bug, they would simply go back or go somewhere else.

I do understand that there must be historical reasons for this behavior and I am just looking at a slice of this. All I am saying is that there's lots of confusion. Otherwise this a great piece of tech with tons of potential.
Other problems I would like to point out would be difficulty in debugging and no "internals" page per se (a deadly combo).

has there been any progress on this? Im using getServerSideProps for seo reasons to provide the initial data for some pages, then caching it (using apollo). However when navigating back to the same page (data is already cached) its re-running the getServerSideProps and blocking the navigation until that resolves.

The data fetching in the getServerSideProps isn't super slow, but the process of calling the getServerSideProps is very slow and this leads to a very slow navigation to pages that contain getServerSideProps.

Am I missing something here?

Thanks

I've been excited to try out Next.js for quite some time, but the lack of ability to fetch data clientside on clientside navigation is definitely going to hold me back from using it with my current tech stack.

The project in question uses firebase (firestore) as a data store. I want SSR for SEO/Social cards, but I don't want users of the website to have to wait on server side data fetching to move around in the application. With firestore I set up client side listeners which return from a local firestore cache first (fast) and then subsequently validate the data from the server. If every page load (even via client side navigation) must wait on a server side data fetch (which has no cache) it will significantly slow down the experience for every user of our app (other than first time direct link visitors).

@leerob I've seen you write a lot about using firebase/firestore with Next.js, so I'm curious if you ever ran into this issue or if you have any ideas about how to work around this.

has there been any progress on this? Im using getServerSideProps for seo reasons to provide the initial data for some pages, then caching it (using apollo). However when navigating back to the same page (data is already cached) its re-running the getServerSideProps and blocking the navigation until that resolves.

@JClackett Did you add cache-control headers to the request? It sounds like you're using the Apollo cache which is on the client-side.

but the lack of ability to fetch data clientside on clientside navigation is definitely going to hold me back from using it with my current tech stack.

@goleary You can fetch data client-side using React via vanilla useEffect() or a library like SWR.

I want SSR for SEO/Social cards, but I don't want users of the website to have to wait on server side data fetching to move around in the application.

How frequently does the data change? You can also use getStaticProps/getStaticPaths along with SWR on the client side. At build time, you fetch the initial data and then forward it to the React component. The SWR cache is seeded with the initial data and then updates on the client-side. You could also set revalidate on gSP to invalidate the cache after [x] seconds/minutes/etc.

https://swr.vercel.app/docs/with-nextjs#pre-rendering

Thanks for the quick response @leerob!

How frequently does the data change? You can also use getStaticProps/getStaticPaths along with SWR on the client side. At build time, you fetch the initial data and then forward it to the React component. The SWR cache is seeded with the initial data and then updates on the client-side. You could also set revalidate on gSP to invalidate the cache after [x] seconds/minutes/etc.

On the order of a few times per day. I think this approach would work for my use case although I can see it resulting in a unappealing flashing of content as soon as the client side hook runs if there is a difference between the SSG version and what's presently in firestore.

I would also imagine that with this set up the server is rebuilding pages when not necessary as a user is paging around the app since they already have the most up to date version of the data client-side.

I would also imagine that with this set up the server is rebuilding pages when not necessary as a user is paging around the app since they already have the most up to date version of the data client-side.

The server will only rebuild the page if the time specified in revalidate has passed. For a tangible example of this (it can be hard to understand without looking at something real), check out this blog post.

Thanks for the link, I'll have to play around with static regeneration as it does sound promising for this use case.

@goleary You can fetch data client-side using React via vanilla useEffect() or a library like SWR.

Oh and to this, yes I realize. I should have said: "the lack of ability to fetch data clientside on clientside navigation _without blocking on server data fetching_ is definitely going to hold me back from using it with my current tech stack."

I still wish I could enable a setup that uses getServerSideProps on initial page load and then relies entirely on client side data fetching for any subsequent navigations. It sounds like the end result as far as user experience goes will be difficult to distinguish from using ISR, but the implementation and clarity of the code used to arrive there may be different.

So.... there are two ways it looks like to solve this problem.

The first is that the page is aware whether other pages should have the props it needs (either via config on the page or via another method).

The second is to _pass_ the props from one page to another (if it has them).

I think the latter makes a whole lot more sense.

Take these scenarios:

  1. The user goes to the homepage, types in a search query and hits a one of the autocompleted results.
  2. The user goes to a listing page of items (blog, products, whatever) and clicks on one of the items.

In the first scenario, you may not have all the props the page needs, and it would be better/faster to let Next.js go and retrieve them.

In the second scenario, you may have everything that is needed and can pass those props to the page.

Therefore, to me, it seems better to add a propertly to the <Link> compoment that accepts a callback function to retrieve the props (when it is in view or whatever).

Some have mentioned that this might lead to mistakes in constructing the <Link> but it is pretty simple to wrap the <Link> component for reuse, and from what I've seen is pretty common in large applications.

As an example:

function ProductLink({ id, title }) {
    // fetchProductFromIndexedDb() returns a promise that resolves to an
   //  an object, or undefined if it doesn't exist.
   const fetchProduct= useCallback(() => fetchProductFromIndexedDb(id)), [
     id,
   ]);

  return (
    <Link href="/product/:id" as={`product/${id}`} propFetcher={fetchProduct}>
       <a>{title}</a>
   </Link>
  );
}

If the props can't be retrieved (i.e. they don't exist locally), Next.js can fallback to it's default behavoir and load the page.

In the example I just gave it could be a method on the page itself (since the only dependency is the id which is passed in the URL), but let's instead consider something like this:

function ProductLink({ id, title, description, imgUrl }) {
   const fetchProduct= useCallback(() => ({
      id,
      title,
      description,
      imgUrl 
    }), [
      id,
      title,
      description,
      imgUrl
   ]);

  return (
    <Link href="/product/:id" as={`product/${id}`} propFetcher={fetchProduct}>
       <a>{title}</a>
   </Link>
  );
}

In a scenario like this, the page doesn't have the context (a listing page) of the page you were on previously, so there is no way for it to retrieve client side props (other than making an API request, which Next.js will do for you anyways).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

renatorib picture renatorib  路  3Comments

swrdfish picture swrdfish  路  3Comments

sospedra picture sospedra  路  3Comments

timneutkens picture timneutkens  路  3Comments

YarivGilad picture YarivGilad  路  3Comments