Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Defer getServerSideProps on client-side navigation #13910

Closed
rafaelalmeidatk opened this issue Jun 8, 2020 · 38 comments
Closed

Defer getServerSideProps on client-side navigation #13910

rafaelalmeidatk opened this issue Jun 8, 2020 · 38 comments
Milestone

Comments

@rafaelalmeidatk
Copy link
Contributor

Feature request

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

When clicking in a link to a page that uses getServerSideProps, the navigation will be kept in hold until the server response is complete, then the navigation will start. I want to be able to defer the getServerSideProps data-fetching until the client-side navigation has been completed. Similar to the behavior of getStaticProps with fallback: true.

Describe the solution you'd like

  • When directly accessing the page (e.g., typing the URL and pressing Enter), it should be server-side rendered
  • When navigating to the page from another page, the navigation should happen immediately. The page will be rendered with a loading state. When the navigation is completed, the gSSP request should be fired to fetch the content and send it through the page props.

I should be able to opt-in this behavior with a flag in the return of gSSP. The router should have a flag to tell if the page is fetching data:

// pages/example.js

function Post({ post }) {
  const router = useRouter()

  // If the page data is not yet fetched, this will be displayed
  // initially until getServerSideProps() finishes running
  if (router.isFetchingAfterDefer) {
    return <div>Loading...</div>
  }

  // Render post...
}

export async function getServerSideProps(context) {
  return {
    props: { ... },
    deferOnClientSideNavigation: true,
  }
}

Describe alternatives you've considered

Using getInitialProps/getServerSideProps to detect if it is client-side navigation and send empty props, then repeat the fetch logic in the component code

@sromexs
Copy link
Contributor

sromexs commented Jun 12, 2020

Hi
I'm working an next js these days and i want to do something cool. when user make http request to my page i (for SEO) i want page render in Server Side and show all data at ones (i want fetch data at Server Side). but when page load completed and user navigate between pages using next/lint i want data render in Client Side (i want fetch data in Client Side). with getInitialProps we can do this let me show you an example.

function Index({ posts }) {
  const [postsArr, setPostsArr] = useState(posts);

  useEffect(() => {
    async function fetchNewPosts() {
      const res = await fetch("https://getpost.com/");
      setPostsArr(await res.json());
    }

    if (posts.length === 0) {
      fetchNewPosts();
    }
  }, []);

  return (
    <>
      <Nav />
      <p>Hello World May I Pick Banana!</p>
      <pre>{JSON.stringify(postsArr, null, 4)}</pre>
    </>
  );
}

Index.getInitialProps = async ({ req }) => {
  let posts = [];
  if (req) {
    const res = await fetch("https://getpost.com/");
    posts = await res.json();
  }
  return { posts };
};

export default Index;

in this approach if there is request we fetch data at first place (Server Side Rendering) but if we navigating to this page with next/link it fetches data from client side (Client Side Rendering).
But with these new data fetching methods getStaticProps and getServerSideProps we cant able to do above behavior.
please fix this issue in new versions.

@Timer Timer added this to the backlog milestone Jun 17, 2020
@JonCognioDigital
Copy link

JonCognioDigital commented Aug 19, 2020

@sadraromexs

That's exactly what I've done in my previous 2 NextJS projects. Since starting my new project I've taken the official advice and am doing everything using getServerSideProps (because I believe the current model for getStaticProps is totally broken).

My old sites were lightning fast, the new one is significantly slower, because making a trip to the API on every request is obviously going to be slower. Seems like a bad decision.

@ziimakc
Copy link

ziimakc commented Oct 24, 2020

It's obvious that user should not wait a few seconds in which nothing happens (because getServerSideProps is not finished loading) when he clicks a link. He should see some action is happening, for example:

  • Loading spinner
  • Data template (boxes for images, text and so on), youtube example.

Just one question, how then you will skip graphql parallel query with router.isFetchingAfterDefer?

export function Test(): JSX.Element {
  const { data, error } = useTestQuery() // will it fire alongside getServerSideProps?

  return (
    <Main>
          {JSON.stringify(data)}
    </Main>
  )
}

export const getServerSideProps: GetServerSideProps = async () => {
  const apolloClient = initializeApollo()

  await apolloClient.query({ query: TestQuery })

  return {
    props: {
      initialApolloState: apolloClient.cache.extract(),
    },
  }
}

@fr3fou
Copy link

fr3fou commented Nov 21, 2020

Any news on this issue? I'll be developing a project which has similar requirements soon and want to know whether to use getInitialProps or getServerSideProps.

@barcel0
Copy link

barcel0 commented Jan 9, 2021

Same problem here. It’s my first NextJS app and I’m not sure how to handle this situation. I’m probably going to end up using getInitialProps despite of being getServerSideProps the recommended option according to the documentation. Any workaround so far?

@JonCognioDigital
Copy link

Still waiting to see what happens with this. They say that getInitialProps is still going to be supported but it's been heavily demoted in the documentation and my guess is that it will stay there but won't be worked on again (just a guess but seems reasonable).

The trouble is, the more I think about this issues the more I think it's intertwined with other issues do do with Data fetching. GetStaticProps should also work this way (blocking when generating SSG, not blocking when client side navigation) and we hope to see improvements that allow data-fetching at app/path/component level not just page level. All of this is tied together and needs a solution which brings it all together.

My hope would be that Vercel would open up an RFC for people to comment on a new props fetching solution where we can discuss all of these issues on one place and find a single solution. That's ot something for a minor release though.

@timneutkens
Copy link
Member

timneutkens commented Jan 11, 2021

The solution to this issue is covered by https://reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html and it's interplay with concurrent mode / suspense. Can't share the exact details yet, will keep you posted.

@vaniyokk
Copy link

vaniyokk commented Mar 1, 2021

Any activity on this?
It's really annoying problem!
Very common plugins (next-i18next, next-translate) are already dropped support for getInitialProps. All new examples are using getServerSideProps as preffered solution and there is NO WAY to eliminate extra network call every time route changed?
Nobody uses client-side cache? Hmm... don't believe.
Waiting for experimental react feature is not a solution for now.

You really need to think about it. I don't understand why this issue takes so little attention from community...

@ianldgs
Copy link
Contributor

ianldgs commented Mar 1, 2021

Any activity on this?
It's really annoying problem!
Very common plugins (next-i18next, next-translate) are already dropped support for getInitialProps. All new examples are using getServerSideProps as preffered solution and there is NO WAY to eliminate extra network call every time route changed?
Nobody uses client-side cache? Hmm... don't believe.
Waiting for experimental react feature is not a solution for now.

You really need to think about it. I don't understand why this issue takes so little attention from community...

FYI, next-i18next recently release version 8.0 that doesn't add a getInitialProps on App anymore.

@JonCognioDigital
Copy link

You really need to think about it. I don't understand why this issue takes so little attention from community...

Couldn't agree with this more.

There should be no server side call on client-side navigation. That should be the default way data-fetching works unless you opt out of it. It's especially annoying seeing as this was possible before (and technically still is if you're prepared to build a new site on deprecated features).

@Kae7in
Copy link

Kae7in commented Mar 9, 2021

I really hope this feature is added because getServerSideProps is slowing down our site quite tremendously. Language used around getInitialProps makes it seem like it will be deprecated soon, and getStaticProps/Paths just doesn't make sense for our use-case because our pages have a lot of dynamic data, some of which depends on the user.

@mapsgeek
Copy link

wow, i didn't realize how fast and convenient NuxtJS async Fetch until i came to React nextjs!
don't understand why it's so hard to come up with a similar concept.

@timneutkens
Copy link
Member

Language used around getInitialProps makes it seem like it will be deprecated soon

It's not deprecated / will not be deprecated soon. We just recommend getStaticProps/getServerSideProps for the majority of use cases.

@mapsgeek
Copy link

mapsgeek commented Mar 11, 2021

@timneutkens how does this solve the majority of use cases since serverSideProps is meant for SSR but now no one would want to have a slow page load on client-side navigation!
so it's totally unusable for SSR, i know many use cases are towards SSG but i don't think it's as much as people who want to dynamically generate their page, just take a look at Nuxtjs fetching method !!

@Kae7in
Copy link

Kae7in commented Mar 11, 2021

Language used around getInitialProps makes it seem like it will be deprecated soon

It's not deprecated / will not be deprecated soon. We just recommend getStaticProps/getServerSideProps for the majority of use cases.

Noted. That's good because we changed our implementation over to getInitialProps() and now our site is tremendously faster AND we're getting the SEO optimizations we need. I do have trouble seeing many use cases for getServerSideProps() given how slow it is, so I hope there's more clarity provided on this at some point.

@danoc
Copy link

danoc commented Mar 11, 2021

Language used around getInitialProps makes it seem like it will be deprecated soon

It's not deprecated / will not be deprecated soon. We just recommend getStaticProps/getServerSideProps for the majority of use cases.

Noted. That's good because we changed our implementation over to getInitialProps() and now our site is tremendously faster AND we're getting the SEO optimizations we need. I do have trouble seeing many use cases for getServerSideProps() given how slow it is, so I hope there's more clarity provided on this at some point.

I've found getServerSideProps to work well when used with plain <a> elements. getServerSideProps feels particularly slow when used with Next.js's <Link> component since there's no indication to the user that the browser is fetching data when they clicked on a <Link>.

A good use case for getServerSideProps is if you're importing heavy NPM dependencies that you don't want to include in the client bundle. AFAIK getInitialProps can't do that.

You'll miss out on the automatic prefetching if you just use <a> elements, but I suppose it'd be possible to build a custom <ServerSideLink to="/about"> component that uses IntersectionObserver to detect when it is in view and calls router.prefetch(to) to prefetch the static assets without and without doing client-side navigation. I haven't actually tried this though. Maybe it's a bad idea. 🤷🏻

@muhammetsait
Copy link

Hello everyone 👋

I was facing this problem in my project and I ended up using this as a workaround (hacky as hell but seems to work):

const isServerReq = req => !req.url.startsWith('/_next');

function Post({ post }) {
  // Render post...
}

export async function getServerSideProps({ req }) {
  const initData = isServerReq(req) ? await fetch(...) : null;

  return {
    props: {
      initData,
    },
  }
}

@DonovanCharpin
Copy link

Thanks god I found this thread..

I'm using NextJS since the beginning (> 3yr) and always upgraded with the new versions to get the new features. I'm using other libraries like next-routes, next-redux-wrapper, next-i18next and I cannot upgrade all of them anymore.

I did all the work to migrate from getInitialProps to getServerSideProps for my SaaS and getStaticProps for the website but the result is just not what I was expecting. The fact that getServerSideProps is called on every client side routing is just a no go.

At the end I felt like NextJS was just a nice solution for e-commerce and blogs but was not the solution to create a complex app because the current 3 methods (SSR/Path/Static) just don't answer the issue that getInitialProps alone was doing perfectly. I would prefer focusing on my features instead of reading all the possible discussions (GitHub/SO/Reddit) to find workarounds that should be normal behaviour (or at least fallback).

Because of the doc, the other libraries are just like "getInitialProps will be removed soon so we will just stop supporting it at all now" (from the libraries owners).

It's really annoying because we really want to migrate and get the new features but since v10 we got some regressions related to general use cases so we cannot fully enjoy the optimizations or update others libraries.

I'll keep getInitialProps for now but I'll also keep my refactor branch active, waiting for news about a real SSR.

I'm sure you have everything in the NextJS core to do something great though. Please don't only focus on blog/e-commerce, there are plenty of great/complex app that need the most advanced features of SSR! 🙏

@JonCognioDigital
Copy link

Language used around getInitialProps makes it seem like it will be deprecated soon

It's not deprecated / will not be deprecated soon. We just recommend getStaticProps/getServerSideProps for the majority of use cases.

That's good to know. But I think we'd all be interested to know why you're pushing the slower, less efficient new way of doing things for "the majority of use cases" when the old way is much faster? What benefit do we get from doing a server side call on every client-side navigation? Isn't there a way of changing it to get the best of both worlds?

It seems that the team are quite dismissive of people's concerns over this and maybe don't understand the problem?

@ianldgs
Copy link
Contributor

ianldgs commented Apr 14, 2021

Hello everyone 👋

I was facing this problem in my project and I ended up using this as a workaround (hacky as hell but seems to work):

const isServerReq = req => !req.url.startsWith('/_next');

function Post({ post }) {
  // Render post...
}

export async function getServerSideProps({ req }) {
  const initData = isServerReq(req) ? await fetch(...) : null;

  return {
    props: {
      initData,
    },
  }
}

Based on this reply, I've come up with a high order function to handle this in a more elegant way:

/**
 * Do not SSR the page when navigating.
 * Has to be added right before the final getServerSideProps function
 */
export const hasNavigationCSR = (next?: GetServerSideProps) => async (
  ctx: GetServerSidePropsContext,
) => {
  if (ctx.req.url?.startsWith('/_next')) {
    return {
      props: {},
    };
  }
  return next?.(ctx)!;
};

And I add this to all my pages:

function getProps(ctx) {
  // my logic
}

export const getServerSideProps = hasNavigationCSR(getProps);

Been using high order functions to features that I need to attach to a page, such as authorization, translation, initializing apollo client, etc.

It's a workaround, but works well and looks elegant with the other features: hasTranslations(hasApollo(hasAuth(hasNavigationCSR(getProps))))

If anyone doesn't want to wait for an official implementation, like me, that's what I'd recommend.

@dimisus
Copy link

dimisus commented Apr 19, 2021

A similar approach to the above "solution" by adding isCSR to the context with a factory function:

import { curry } from 'lodash/fp';

const gSSPWithCSRFlag = curry((gSSP, context) => {
  if (gSSP && context) {
    // Set a flag to context that checks if the request
    // is coming from client side routing/navigation
    context.isCSR = Boolean(context.req.url.startsWith('/_next'));

    return Promise.resolve(gSSP(context));
  }

  throw Error('Either context or gSSP is not provided');
});

export default gSSPWithCSRFlag;
export const getServerSideProps = gSSPWithCSRFlag(async ({ isCSR, ...restCtx }) => { ...

@nghiepdev
Copy link
Contributor

I'm still facing this problem, currently my project using GraphQL and next-urql.
I have seen them solve this problem using by using react-ssr-prepass
https://github.com/FormidableLabs/urql/blob/main/packages/next-urql/src/with-urql-client.ts#L144

May be the best solution for now: still using getInitialProps instead of getServerSideProps and react-ssr-prepass

@mapsgeek
Copy link

@nghiepit i'm facing this problem and using GraphQL as well and i can confirm that next-urql was the only "easy out of the box non hacky" way to resolve the issue specially that all the examples was about rest and i couldn't implement a similar with Apollo

@martinnabhan
Copy link
Contributor

martinnabhan commented Apr 22, 2021

Since we know when a route change starts and completes I don't see how this is a problem.
For example, using a hook:

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

const useRouteChange = () => {
  const router = useRouter();
  const [routeChanging, setRouteChanging] = useState(false);

  const handleRouteChangeComplete = () => setRouteChanging(false);
  const handleRouteChangeStart = () => setRouteChanging(true);

  useEffect(() => {
    router.events.on('routeChangeComplete', handleRouteChangeComplete);
    router.events.on('routeChangeStart', handleRouteChangeStart);

    return () => {
      router.events.off('routeChangeComplete', handleRouteChangeComplete);
      router.events.off('routeChangeStart', handleRouteChangeStart);
    };
  }, [router]);

  return { routeChanging };
};

export { useRouteChange };

Then a Spinner component:

const Spinner = () => {
  const { routeChanging } = useRouteChange();
  const [shouldShowSpinner, setShouldShowSpinner] = useState(false);

  const timer = useRef<NodeJS.Timer>();

  useEffect(() => {
    if (routeChanging) {
      // Only show spinner on page loads taking longer than 100ms.
      timer.current = setTimeout(() => setShouldShowSpinner(true), 100);
    } else {
      setShouldShowSpinner(false);

      if (timer.current) {
        clearTimeout(timer.current);
      }
    }
  }, [routeChanging]);

  if (!shouldShowSpinner) {
    return null;
  }

  return spinner component here;

Then in say _app.tsx (could be used anywhere you want):

const App = ({ Component, pageProps }: AppProps) => (
  ...
    <Component {...pageProps} />
    <Spinner />
  ...
);

I use a timeout to only show the spinner on pages taking longer than 100ms, but if you want to show the spinner on every navigation the logic gets even simpler!

With this we get SSR without page reloads, and the user gets feedback when pages are loading.

For me what's missing is preloading of getServerSideProps data, just like with getStaticProps.
As it is now getServerSideProps is only called after clicking on a <Link>, which is why it feels slow.
Should be super simple to implement (unless there's something I'm missing), not sure why it's not working that way already.

@timneutkens, do you know if this is in the pipeline? I haven't look at the code for next/link yet, if this is something that could be merged I might open a pull request!

Edit:
I'm sorry! It seems you've already answered this question here:
#11578 (comment)

@zulqarnain-empg-zz
Copy link

any updates on this issue?

@PandaProgrammingHub
Copy link

Hi All, any updates on this issue? do we have any alternative to achieve this functionality?

@Vincz
Copy link

Vincz commented May 13, 2021

Hey guys. Any update about this? I have a similar use case with firebase/firestore. I Want to query it with firebase-admin server side, but with regular firebase client on the client side. This feature seems missing and is necessary to do proper SSR IMHO.

@TrustyTechSG
Copy link

Im coming from CRA just months ago, and I must say I didn't realize how fast CRA is until I converted the App to NextJS (From a complex Web App point of view).

@emil-wearealldigital

This comment has been minimized.

@GaryAlway
Copy link

Hi, any update on this?
Having the client block page transitions like this leads to a really poor user experience IMO.
I looked into the beta next-page-transitions but that doesn't help solve this particular problem.

@sromexs
Copy link
Contributor

sromexs commented Sep 11, 2021

i don't believe why this important issue should last this long :(

@vtv6
Copy link

vtv6 commented Sep 11, 2021

i think this new patch can solve this problem https://github.com/vercel/next.js/releases/tag/v11.1.2

@sromexs
Copy link
Contributor

sromexs commented Sep 11, 2021

i think this new patch can solve this problem https://github.com/vercel/next.js/releases/tag/v11.1.2

How Exactly ?? can you describe ?

@vtv6
Copy link

vtv6 commented Sep 11, 2021

i think this new patch can solve this problem https://github.com/vercel/next.js/releases/tag/v11.1.2

How Exactly ?? can you describe ?

I'm sorry, my mistake :)))

@ghost
Copy link

ghost commented Sep 21, 2021

@Kyoij
Do you have any idea how to use getServerSideProps should support props value as Promise #28607 to make a loading state?

@mycolaos
Copy link

Also looking for the solution to use getServerSideProps only on server. Is it really that hard to add an option to disable the fetch on the client? Can somebody help to find where to look in the Next.js repo to code something like that? I would try to create and use a cloned repo with such solution.

@jeroenvisser101
Copy link

I wrote a proposal/RFC for an server-side only version of getServerSideProps, getInitialServerSideProps (ran only on the initial request, subsequent client-side navigation will have to do data fetching manually, for instance, with Apollo). I'm interested to hear if this would be a solution for the issues described here.

I'm working on a working prototype too, will share when ready.

@ahsankhan201010
Copy link

ahsankhan201010 commented Nov 13, 2021

One of the main issue is still not resolved. This delay b/w clicking and actual navigation is one of the biggest flaw in UX dictionary. Any impatient user may click several time as there is zero response on frontend side (even the link takes time to change)

Tried to bypass getSeverSideaProps by checking the request link but still that small delay exist because, a page having serverSideProps function must call that function before the actual navigation (that's annoying)

They should provide some workaround in Link component (like shallow prop does - but shallow prop work for the links within the same page), so a developer would be able to bypass that function call from navigation cycle (click -> getServerSideProps() -> page load)

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests