Next.js: 最小的阿波罗示例

创建于 2016-12-13  ·  60评论  ·  资料来源: vercel/next.js

事实证明,当直接使用 apollo-client 而不是 react-apollo 时,apollo 集成要容易得多。

这是代码: https ://github.com/nmaro/apollo-next-example
这是一个正在运行的版本(至少只要我保持 graphql 服务器在线): https ://apollo-next-example-oslkzaynhp.now.sh

相关细节在这里:

阿波罗.js

import ApolloClient, {createNetworkInterface} from 'apollo-client'

export default new ApolloClient({
  networkInterface: createNetworkInterface({
    uri: GRAPHQL_URL
  })
})

然后在一个页面中

import React from 'react'
import gql from 'graphql-tag'
import 'isomorphic-fetch'
import apollo from '../apollo'
import Link from 'next/link'

const query = gql`query {
  posts {
    _id
    title
  }
}`
export default class extends React.Component {
  static async getInitialProps({req}) {
    return await apollo.query({
      query,
    })
  }
  render() {
    ...
  }
}

最有用的评论

我们应该在 Apollo 博客上获得一篇关于 Apollo + Next.js 的博文!

所有60条评论

一些观察:这种方法不会深入到组件来加载它遇到的所有 graphql 查询(您可以使用 react-apollo 启用服务器端)。

我相信这对于 next.js 来说有点问题:你并不是真的要在组件层次结构中加载数据——如果你希望它在客户端和服务器端都发生的话。 加载数据只有一点:在根组件中的 getInitialProps 中。 我不知道这是否会在未来改变,但如果没有,那么我们将不得不

  1. 构建我们的应用程序,以便我们从一开始就加载页面的所有相关数据,或者
  2. 仅在客户端上的一部分数据(即我们没有在 getInitialProps 中加载的所有数据),使用不同的策略

在这两种情况下,上述方法都应该适用于在 getInitialProps 中加载的数据。

如果一些核心开发人员喜欢这个,我可以使用示例创建一个拉取请求。

关于 getInitialProps 仅在 root 调用,请参阅https://github.com/zeit/next.js/issues/192。 很想在那里有你的想法。

@sedubois您在使用react-apollo时遇到了什么问题?

@nmaro你的https://github.com/nmaro/apollo-next-example是空的。

@amccloud最好向@nmaro询问(我仍然需要回到代码中)。

感谢@sedubois ,它现在在线(总是忘记运行push origin master而不是第一次运行 $ push )。

糟糕,我提到了错误的人。 @nmaro你对 react-apollo 有什么问题?

数据已加载到服务器中,然后一旦客户端开始加载,页面就再次为空。 然后我查看了@sedubois的实现(https://github.com/RelateMind/relate),并认为它对于快速的概念验证已经相当复杂,所以我最终尝试了较低级别的 api。

@stubailo ,因为您想知道为什么将 apollo 与 next.js 集成如此困难 - 看起来您可以在客户端和服务器上获取数据的唯一位置是位于名为 getInitialProps 的异步函数内的页面根组件。 我认为集成 react-apollo 的常用方法只在客户端有用。

有趣 - Next.js 是否有任何其他数据集成? 根据我看到的示例,看起来使用 Redux 也很困难。

大多数现代数据系统都有某种全局缓存(Redux、Apollo、Relay),所以我觉得 Next 中需要某种设施来启用它。

我们如何才能让 Next.js 与具有全局缓存的现代数据系统(Redux、Apollo、Relay)更好地配合使用? 我觉得这应该是下一个版本的重中之重。 @stubailo @rauchg

绝对地。 我们在 wiki 上有一个 Redux 示例,我们需要创建更多类似的示例 :)

顺便说一句,这不是我们必须在发布的基础上做的事情。 我们可以随时写一个 wiki 教程。

顺便说一句@nmaro这个例子看起来很整洁,感谢您的贡献。 我们可以以此为基础并扩展它

哦,奇怪 - 我没有意识到所涉及的问题。 @nmaro react-apollo 是什么让事情变得困难? 似乎您应该能够几乎完全遵循redux 示例,但是在使用createStore $ 的地方执行new ApolloClient $ ,并使用ApolloProvider而不是Provider

我很想和某人一起做一个最小的例子。 这是我们的 React 的“hello world”示例,如果有 Next.js 的端口会很棒: https ://github.com/apollostack/frontpage-react-app

@stubailo我很想和你一起做一个最小的例子。 我一直在使用通用 apollo 微框架 Saturn 进行几个项目,并希望最终将它们移植到 Next.js + Apollo :)

很好 - 是的,只需对首页应用程序进行最小的修改以使其在 next.js 而不是 create-react-app 上运行将是我的偏好。 然后我们也可以在我们的主页上列出它!

@stubailo

一个小问题是数据在服务器上加载和渲染,只是在客户端加载时被替换为什么 - 我想我只是不知道阿波罗和下一个足以让它正确。 直接使用 apollo-client 我没有这个问题。

服务器渲染更棘手的是,如果您在层次结构中进行更深层次的查询。 React 没有异步渲染事物的方法,即在渲染之前等待每个组件准备好。 这意味着 ssr 框架要么必须

  1. 遍历整个组件树两次,一次加载数据,一次渲染数据。
  2. 在根目录中提供一个异步入口点 - 这是使用 getInitialProps 的 next.js 方法

现在的问题是,apollo 是否有办法检测渲染组件树所需的所有数据调用,并在一个可以提供给 getInitialProps 的函数调用中完成所有这些操作。

@stubailo有办法解决这个问题吗? ^

@nmaro @ads1018你见过getDataFromTree吗? 如在我的示例中使用的那样: https ://github.com/RelateMind/relate/blob/master/hocs/apollo.js

顺便说一句,我想知道现在合并https://github.com/zeit/next.js/pull/301是否可以简化事情。 还没有调查那个。

@sedubois我检查了,谢谢分享! 是的,我想您使用 react-apollo 的示例可以使用刚刚合并到 Master 中的新编程 API(#301)来简化,这样您就不必用自己的 HOC 包装所有页面组件。 如果您在这方面取得任何进展,请告诉我! 在 apollo 主页上获得 next.js 示例会很酷:)

NB @ads1018 https://github.com/zeit/next.js/pull/301是关于使用 CommonsChunkPlugin 提取公共代码,而不是 Programmatic API。 但是,是的,编程 API 肯定也会有所帮助,期待它发布。

有没有人幸运地让 react-apollo 与新的 2.0.0-beta.2 版本一起工作?

@sedubois @stubailo如果你想看的话,我推了下一个 + react-apollo 的尝试。 你可以在这里找到它: https ://github.com/ads1018/frontpage-next-app

我现在面临的一个问题是组件只在客户端而不是服务器端呈现。 也许我们可以在 server.js 中使用 react-apollo 的getDataFromTree方法? 或者也许在我们自己的自定义<document>中? 欢迎任何建议/拉取请求!

希望最终将这个 hello world 示例包含在 Next 示例文件夹和 Apollo 主页中。

服务器渲染数据的唯一先决条件是它作为getInitialProps中的对象返回,不需要覆盖。

明白了。 我认为这对 react-apollo 来说有点困难,因为正如@nmaro指出的那样:

问题是 apollo 是否有办法检测渲染组件树所需的所有数据调用,并在一个可以提供给 getInitialProps 的函数调用中完成所有这些操作。

明白了

@ads1018稍加研究,如果顶层组件暴露在 getInitialProps 上,则可以使用Apollo 助手将其渲染为字符串。

_document 将类似于:

export default class MyDocument extends Document {
  static async getInitialProps ({ app }) {
    const wrapped = React.createElement(ApolloProvider, { client }, app)
    const rendered = await renderToStringWithData(wrapped)
    return { html: rendered, initialState: client.store.getState() }
  }

  render () {

    return (
      <html>
        <Head>
          <title>My page</title>
        </Head>
        <body>
          <ApolloProvider client={client}>
            <Main />
          </ApolloProvider>
          <NextScript />
        </body>
      </html>
    )
  }
}

@rauchg除了renderPage之外,公开app似乎是一个简单的更改,但是有什么我忽略的吗?

@bs1180啊太棒了。 这就是我一直在寻找的。 希望这是公开app的简单更改。 它会立即使 Next 成为一个对客户端友好的 graphql 框架。

@bs1180我在renderPage return object中暴露app 。 这与你的想法一致吗?

@ads1018不完全 - 在您的版本中render仍在被调用,如果renderToStringWithData将被手动调用,这将是不必要的重复。

我在这方面做了更多的工作,但我的最终结果并不像我最初想象的那么漂亮,主要是因为主应用程序被渲染为<Main />组件的子组件(进入 __next div),这很糟糕从上面传递给您的应用程序的任何上下文。 所以它仍然需要一个 HOC 来再次添加 Apollo 上下文。

@bs1180我明白了。 是否可以将<Main />渲染为ApolloProvider的子级,以便我们可以传递上下文?

我不确定你的意思,但我认为这是错误的方向。 只需一个 HOC 就可以实现完美的 SSR - 这是我的拼凑版本作为起点:

export default (options = {}) => Component => class ApolloHOC extends React.Component {
  static async getInitialProps (ctx) {
    const user = process.browser ? getUserFromLocalStorage() : getUserFromCookie(ctx.req)
    const jwt = process.browser ? null : getJwtFromCookie(ctx.req)

    if (options.secure && !user) {
      return null // skip graphql queries completely if auth will fail
    }

    const client = initClient(jwt)
    const store = initStore(client)

   // This inserts the context so our queries will work properly during the getDataFromTree call,
   //  as well as ensuring that any components which are expecting the url work properly 
    const app = React.createElement(ApolloProvider, { client, store },
      React.createElement(Component, { url: { query: ctx.query }}))

 // this is the most important bit :)
    await getDataFromTree(app)

    const initialState = {[client.reduxRootKey]: {
      data: client.store.getState()[client.reduxRootKey].data
    }}

    return { initialState, user }
  }

  constructor (props) {
    super(props)
    this.client = initClient()
    this.store = initStore(this.client, this.props.initialState)
  }

  render () {
    return (
      <ApolloProvider client={this.client} store={this.store}>
          <Component url={this.props.url} />
      </ApolloProvider>
    ) 
  }
}

initClientinitStore以 redux 示例为模型。 然后每个页面看起来像这样:

import ApolloHOC from '../hoc'
import { graphql } from 'react-apollo'

export default ApolloHOC({ secure: false })(() => <b>Hello world</b>)

希望这很有用 - 我很想知道是否有其他途径可以调查,或者我忽略了什么。

@bs1180酷,这非常有用,谢谢分享。

我们还能用_document.js内的 graphql 数据渲染页面吗? 如果我们能像你最初提议的那样一起绕过 HOC,那就太好了。

我不这么认为——据我所见,客户端渲染将从自定义_document.js中删除上下文中传递的任何内容(无论是 Apollo 客户端、标准 Redux 商店、主题等)。 尽管可以将一些 Apollo SSR 逻辑移到那里,但仍然需要某种 HOC/包装器组件来将必要的对象添加回上下文中。
不过,对 next.js 内部有更好了解的人可能会有更好的主意。

好吧,如果您设法获得一个可行的示例,那么很乐意检查一下。 我仍在努力让它发挥作用。

我有一个 React Apollo 和 Next 的工作示例 😄 🚀 我希望你们中的许多人都觉得它有用。 你可以在这里查看: https ://github.com/ads1018/next-apollo-example(我还使用 Now 部署了一个演示。)

我最终在我的页面中使用了一个名为withData()的 HOC,它用ApolloProvider包装了页面。 我最初是通过在每页的基础上使用提供程序而不是在单个文件中使用提供程序来关闭的,但我被一些非常聪明的人说服,它对可读性和可伸缩性更好。 我实际上认为withData(MyComponent)看起来相当不错,并且为特定页面获取数据的读者(不是双关语)提供了良好的上下文。

感谢@bs1180@rauchg为我指明了正确的方向。 如果您想在 repo 中添加一个with-apollo示例,请告诉我,我可以创建一个拉取请求。

谢谢@ads1018 😊 和我的例子https://Relate.now.sh相比,这个例子是否解决了在深度嵌套组件中使用 Apollo 的问题(避免 getInitialProps 的级联)? 也许这个例子应该展示这一点,因为它是主要的痛点。 我确信将它添加到示例文件夹中将非常感激。

@sedubois我无法重现您在 #192 中引用的错误。 我在嵌套组件中使用 Apollo 没有任何问题。 如果您拉下我的示例并能够重现它,您会告诉我吗?

感谢@ads1018 ,在https://github.com/ads1018/next-apollo-example/issues/2 🎉 中的修复程序效果很好。 我也更新了我的示例: https ://github.com/RelateNow/relate

干得好, @ads1018 @sedubois! 我一直在关注这个和#192,我也一直在研究使用 Apollo 和 vanilla React 的预取/异步视图。

您是否注意到或预计在每个页面显示之前运行getDataFromTree会出现任何性能问题? 因为从技术上讲,该方法递归地渲染整个树,然后当getInitialProps返回时,React 再次渲染树(尽管使用来自缓存的数据)。

真正好的解决方案👍我认为渲染两次是确保所有子数据都被缓存的唯一选择,只是好奇你们对性能的看法。

@estrattonbailey - 我没有注意到任何性能问题,我也没有预料到任何问题。 事实上,它超级活泼! 至于运行getDataFromTree ,我已经将该方法调用包装在一个条件中,该条件检查我们是否在服务器上,因此只有在用户首次加载应用程序时才会调用它,并且在所有后续路由更改时都会被绕过. 如果您想查看性能,可以使用演示。 如果您有任何反馈,请告诉我!

@ads1018为您的示例提供一些想法:

  • 这样简化initialState
  • 这样的文件中分离中间件、存储和减速器
  • 将 isServer 简化为typeof window !== 'undefined' ,删除 !!ctx.req
  • 将 IS_SERVER const 提取到 lib,无需将其作为参数传递

@ads1018很高兴听到! 不错的小演示。

我想问的是:这个规模会有多大? 虽然我还没有使用 Next,但据我了解,Next 在每个路由转换上调用getInitialProps ,如果在页面组件上可用,即pages/page.js 。 在具有数百个节点和大量数据的完整应用程序/网站上,我想在每条路线上渲染两次可能会导致一些延迟。

我正在做的项目是一个大型的编辑网站,所以我希望对不同的方法进行一些基准测试,包括你的。 如果您愿意,很乐意在Twitter上讨论更多内容。 感谢您的工作!

@estrattonbailey 明白了。 我想它会很好地扩展。 对于初始页面加载, getInitialProps将仅在服务器上执行。 您是正确的, getInitialProps将在客户端上再次执行,但不会请求两次数据,因为getDataFromTree包含在检查我们是否在服务器上的条件中。

旁注 - 如果您担心由于页面上请求的大量组件和数据而导致的初始页面加载时间,您可以随时告诉 apollo 在 SSR 期间有意跳过特定查询并通过传递ssr: false将它们卸载到客户端

如果您想进一步讨论,我会在推特上与您联系 :)

您是正确的,getInitialProps 将在客户端上再次执行,但不会请求两次数据,因为 getDataFromTree 被包装在一个条件中,该条件检查我们是否在服务器上。

重要的是要记住getInitialProps仅在使用<Link>转换时才在客户端执行,而不是在初始加载之后

@ads1018 @estrattonbailey AFAIK 在第一页加载时确实仍然有 2 个服务器端渲染: getDataFromTree 被执行并在内部渲染整个树,然后再次调用渲染以构造 HTML 响应。 不要认为是否有任何方法可以避免这种情况,但由于 SSR 避免了网络往返,我想它仍然相当高效。

我想当 GraphQL 服务器与 Next.js 服务器托管在同一台机器上时性能是最高的,所以如果你关心性能,你可以随时尝试(此时,我使用 Graphcool 为我的应用程序进行原型设计)后端,而 Next.js 与 Now/Zeit World 一起部署)。

@sedubois @estrattonbailey如果我错了,请纠正我,但我们仍然只渲染一次getDataFromTree不会渲染树,它只是返回一个 promise,当我们的 Apollo 客户端存储中的数据准备好时,它会解析。 在 promise 解决时,我们的 Apollo 客户端存储将完全初始化,如果我们想在 HTTP 请求的响应中传递字符串化结果,我们可以选择渲染树,但在我的示例中我们没有这样做。

getDataFromTree 不渲染树

@ads1018 AFAIK 并查看Apollo 代码,它_does_ 递归地渲染树(只是为了触发 Apollo 数据获取)。 所以这是 2 在第一页加载时呈现服务器端。

但无论如何,多亏了你的演示,现在我们在 Apollo 和 Next 之间有了一个可用的集成,我认为关于 Apollo SSR 性能的剩余问题不再有任何特定于 Next.js 的问题。 我建议在那边问这个问题。

@sedubois无论如何,什么是渲染? 我称之为走路和摇树。 似乎优化得很好——抑制setState并且没有与 DOM 协调。

@ads1018好例子! 看起来它也被添加到这里的 Wiki 中,所以这个问题可能会被关闭?

抄送@rauchg

我们应该在 Apollo 博客上获得一篇关于 Apollo + Next.js 的博文!

@stubailo @ads1018的例子很棒👏 对于使用相同 Apollo 原理的更大的东西,可以检查我的应用程序: https ://github.com/relatenow/relate

谢谢@helfer。 我很高兴它是如何出来的。 我觉得我发现了使用 Next.js + Apollo 开发应用程序的圣杯。 我一直想跟进一篇博客文章,以努力传播福音,但还没有开始。 @stubailo我很乐意与 Apollo 媒体出版物上的一篇文章合作:)

@sedubois 大喊大叫,帮助他完成示例和他的可爱应用程序。 😄

@ads1018很乐意让你上博客。 当您准备好谈论它时,请在 Apollo Slack 上联系我 (thea)。 :)

@helfer你是完全正确的。 我应该做一个新的问题通行证,看看问题是否可以关闭😄

@stubailo @theadactyl 好主意❤️

任何人都知道要注意的问题/公关 - 关于两次服务器端请求数据? 只是好奇

此页面是否有帮助?
0 / 5 - 0 等级