【问题标题】:NextJS: Is there a way to invoke something only on serverside, only on a fresh page load?Next JS:有没有办法只在服务器端调用某些东西,只在新页面加载时调用?
【发布时间】:2021-03-05 01:32:55
【问题描述】:

我们的团队最初创建了一个 _app.tsxgetInitialProps,并包含了一些类似的内容

if (!req) return {};

作为顶行。这个想法是让getInitialProps 仅在服务器端运行,仅在初始页面加载时运行。我们认为这是一个安全的假设,因为通常的建议(经常在 stackoverflow 答案中重复)是 getInitialProps 在页面转换时运行客户端。

我们后来发现getInitialProps 实际上在页面转换时运行服务器端,如果您要转换到具有getServerSideProps 的页面。

所以这不符合我们的目的 - 我们有几个在 getInitialProps 中进行的网络查询,这些查询是在我们的大多数页面转换中进行的(因为我们的大多数页面都有 getServerSideProps),而我们的意图是仅在新页面(应用程序)加载时运行这些查询。

我们可以改为仅在组件挂载时使用 useEffect,但我们希望在服务器端而不是客户端运行这些查询,以防止出现视觉闪烁。

我正在学习如何设置一个包含 setState 的上下文,但初步测试表明它的初始 setState 在客户端和服务器端都运行:

...

const getString = (): string => {
  console.log('about to set string!');
  return 'string';
};

const PagePropsProvider: React.FC<IProps> = ({ pageProps, children }) => {
  const [sample, setSample] = useState<string>(getString());

  return <PagePropsContext.Provider value={pageProps}>{children}</PagePropsContext.Provider>;
};
...

对于像 auth 上下文这样的东西,我想要的是 _app.tsxgetInitialProps 在页面加载时请求初始用户对象服务器端,从道具将其提供给上下文,而不是应用程序在每次页面转换时不必要地重新请求初始用户对象。如果我可以阻止getInitialProps 在页面转换上运行,我可以做到这一点。有没有办法阻止getInitialProps 在这些页面转换上运行?还是一种更惯用的方式来完成我们的目标? (理想情况下,我们宁愿完全避免使用getInitialProps,但getServerSideProps 仍然存在同样的问题,如何使其仅在新页面加载时调用。)

【问题讨论】:

  • 你可以为此使用 cookie 吗?
  • ... 我想,但应用程序可以在不关闭浏览器的情况下卸载并重新加载。而且我很好奇是否有更多的反应/下一个方式来做到这一点。

标签: reactjs react-hooks next.js


【解决方案1】:

getInitialProps 仅在第一次加载时被称为服务器端。在页面转换期间,getInitialProps 仅在浏览器端调用,而 getServerSideProps 在第一次加载时和所有转换期间将被称为服务器端。

如果您只想在首次加载期间从服务器端获取数据,则应仅使用getInitialProps,如下所示:

MyPage.getInitialProps = async () => {
  if (typeof window !== 'undefined') {
    return {} // browser-side, fetch data directly in component
  }

  console.log('server-side !');
  const data = {} // fetch your data
  return { data }
};

然后您可以使用收到的道具设置您的上下文状态。

const PagePropsProvider: React.FC<IProps> = ({ pageProps, children }) => {
  const [state, setState] = useState()
  useEffect(() => {
    if (pageProps.data) {
      setState(pageProps.data)
    }
  }, [pageProps.data])
  return <PagePropsContext.Provider value={state}>{children}</PagePropsContext.Provider>;
};

【讨论】:

  • 很遗憾,这不是我们的选择,因为我们的几个页面确实需要getServerSideProps。当导航到具有getServerSideProps 的页面时,来自_app.jsgetInitialProps 被称为服务器端。
  • 好的,我明白了这个问题。出于好奇,您可以通过getServerSideProps 实现哪些您无法通过getInitialProps 实现的功能?
  • 你是对的...如果我们将所有页面更改为使用getInitialProps 而不是getServerSideProps,问题就会消失。主要的阻力是很明显 NextJS 正在远离getInitialProps,所以感觉就像是倒退了一步。另一个好处是节省了捆绑包的大小,因为getServerSideProps 的内容不会传递给客户端。这些不是交易破坏者,所以如果我们找不到更好的解决方案,这可能是我们前进的方向。不过,我已经向 nextJS 团队建议了一个替代解决方案,我将在另一个答案中提到。
【解决方案2】:

通过仔细检查getInitialPropsgetServerSideProps 中的context 对象,我注意到一个元素似乎可靠地表明了客户端页面转换和新页面加载之间的区别:context.req.url对于客户端页面转换,似乎总是以 /_next/data 开头 - 即使代码本身(getInitialPropsgetServerSideProps)正在服务器端运行。

所以我们正在尝试如下代码:

if (!req || (req.url && req.url.startsWith('/_next/data'))) {
  // return early
}
...

我不确定这对于未来的版本是否可靠或“受支持”,所以我也在此处打开了一个功能请求:

https://github.com/vercel/next.js/discussions/19459

【讨论】:

  • 这是正确的答案。您也可以尝试通过req.headers 来区分客户端和服务器,例如'sec-fetch-mode': 'navigate',但url 似乎更可靠,尽管它仍然很hacky。
猜你喜欢
  • 2021-09-24
  • 2019-04-07
  • 2022-09-27
  • 2012-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多