Next.js: Exemplo de Apollo mínimo

Criado em 13 dez. 2016  ·  60Comentários  ·  Fonte: vercel/next.js

Acontece que a integração do apollo é muito mais fácil ao usar o apollo-client diretamente em vez do react-apollo.

Aqui está o código: https://github.com/nmaro/apollo-next-example
E aqui está uma versão em execução (pelo menos enquanto eu mantiver o servidor graphql online): https://apollo-next-example-oslkzaynhp.now.sh

Os detalhes relevantes estão aqui:

apolo.js

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

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

então em uma página

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() {
    ...
  }
}

Comentários muito úteis

Devemos obter uma postagem no blog sobre Apollo + Next.js no blog Apollo!

Todos 60 comentários

Algumas observações: essa abordagem não se aprofunda nos componentes para carregar todas as consultas do graphql que encontra (algo que você pode habilitar no lado do servidor com react-apollo).

Acredito que isso seja um pouco problemático com next.js: você não deve realmente carregar dados profundamente na hierarquia de componentes - se quiser que isso aconteça tanto no cliente quanto no lado do servidor. Há apenas um ponto para carregar os dados: em getInitialProps no componente raiz. Não sei se isso vai mudar no futuro, mas se não, teremos que

  1. arquitetar nossos aplicativos para que carreguemos todos os dados relevantes para uma página desde o início, ou
  2. uma parte dos dados apenas no cliente (ou seja, tudo o que não carregamos em getInitialProps), com uma estratégia diferente

Em ambos os casos, a abordagem acima deve ser adequada para os dados que estão sendo carregados em getInitialProps.

E se algum desenvolvedor principal gostar disso, posso criar um pull request com o exemplo.

Sobre getInitialProps chamado apenas na raiz, consulte https://github.com/zeit/next.js/issues/192. Adoraria ter suas ideias lá.

@sedubois quais problemas você estava tendo com react-apollo ?

@nmaro seu https://github.com/nmaro/apollo-next-example está vazio.

@amccloud é melhor perguntar ao @nmaro sobre isso (ainda preciso voltar ao código).

Obrigado @sedubois agora está online (sempre esqueça de executar push origin master em vez de apenas push na primeira vez).

Ops, mencionei para a pessoa errada. @nmaro qual problema você teve com o react-apollo?

Os dados foram carregados no servidor, então assim que o cliente começou a carregar a página ficou vazia novamente. Em seguida, olhei para a implementação do @sedubois (https://github.com/RelateMind/relate) e achei que já é bastante complexo para uma prova de conceito rápida, então finalmente tentei com a API de nível inferior.

@stubailo já que você estava se perguntando por que é tão difícil integrar o apollo com o next.js - parece que o único lugar em que você pode buscar dados no cliente e no servidor é no componente raiz da página dentro de uma função assíncrona chamada getInitialProps. Acho que a maneira usual de integrar o react-apollo só seria útil no lado do cliente.

Interessante - existem outras integrações de dados com o Next.js? Parece que usar o Redux também é bem difícil com base nos exemplos que vi.

A maioria dos sistemas de dados modernos tem algum tipo de cache global (Redux, Apollo, Relay), então sinto que precisa haver algum tipo de recurso em Next para habilitar isso.

Como podemos fazer o Next.js funcionar melhor com sistemas de dados modernos com um cache global (Redux, Apollo, Relay)? Eu sinto que isso deve ser uma grande prioridade para o próximo lançamento. @stubailo @rauchg

Absolutamente. Temos um exemplo do Redux no wiki, precisamos criar mais como esses :)

Não é algo que temos que fazer em uma base de lançamento btw. Podemos escrever um tutorial wiki a qualquer momento.

Btw @nmaro esse exemplo parece muito legal, obrigado por contribuir. Podemos tomar isso como base e expandi-lo

Oh, estranho - eu não percebi as questões envolvidas. @nmaro o que é o react-apollo que dificulta as coisas? parece que você deve ser capaz de seguir o exemplo redux quase exatamente, mas faça new ApolloClient onde isso usa createStore , e use ApolloProvider em vez de Provider .

Eu adoraria trabalhar com alguém para fazer um exemplo mínimo. Este é o nosso exemplo "hello world" para React, seria ótimo ter um port para Next.js: https://github.com/apollostack/frontpage-react-app

@stubailo Eu adoraria trabalhar com você em um exemplo mínimo. Eu tenho usado o microframework universal Apollo, Saturn, para alguns projetos e adoraria transferi-los para Next.js + Apollo finalmente :)

Legal - sim, apenas fazer modificações mínimas no aplicativo frontpage para executá-lo em next.js em vez de create-react-app seria minha preferência. então podemos listá-lo em nossa página inicial também!

@stubailo

Um pequeno problema foi que os dados foram carregados e renderizados no servidor, apenas para serem substituídos por nada ao carregar no cliente - acho que simplesmente não sei Apollo e next o suficiente para acertar. Usando o apollo-client diretamente, não tive esse problema.

O que é mais complicado para a renderização do servidor é se você tiver consultas mais profundas na hierarquia. O React não tem como renderizar as coisas de forma assíncrona, ou seja, esperar que cada componente esteja pronto antes de renderizá-lo. O que significa que um framework ssr tem que

  1. passe por toda a árvore de componentes duas vezes, uma para carregar os dados e outra para renderizá-los.
  2. fornecer um ponto de entrada assíncrono na raiz - esta é a abordagem next.js com getInitialProps

Agora a questão é se o apollo tem uma maneira de detectar todas as chamadas de dados que serão necessárias para renderizar uma árvore de componentes e fazer tudo isso em uma chamada de função que pode ser fornecida para getInitialProps.

@stubailo Existe uma solução para isso? ^

@nmaro @ads1018 você viu getDataFromTree ? Conforme usado, por exemplo, no meu exemplo: https://github.com/RelateMind/relate/blob/master/hocs/apollo.js

BTW eu me pergunto se as coisas podem ser simplificadas agora que https://github.com/zeit/next.js/pull/301 está mesclado. Ainda não pesquisei sobre isso.

@sedubois Eu verifiquei isso obrigado por compartilhar! Sim, imagino que seu exemplo usando react-apollo possa ser simplificado com a nova API programática (#301) que acabou de ser mesclada no Master para que você não precise envolver todos os componentes da página com seu próprio HOC. Se você fizer algum progresso nisso, por favor me avise! Seria legal obter um exemplo next.js na página inicial do apollo :)

NB @ads1018 https://github.com/zeit/next.js/pull/301 trata-se de extrair código comum com CommonsChunkPlugin, não API programática. Mas sim a API programática definitivamente ajudará também, ansioso para lançá-la.

Alguém teve alguma sorte em fazer o react-apollo trabalhar com a nova versão 2.0.0-beta.2?

@sedubois @stubailo Eu aumentei minha tentativa de next + react-apollo se você quiser dar uma olhada. Você pode encontrá-lo aqui: https://github.com/ads1018/frontpage-next-app

Um problema que estou enfrentando agora é que os componentes estão sendo renderizados apenas no lado do cliente e não no lado do servidor. Talvez possamos usar o método getDataFromTree do react-apollo dentro do server.js? Ou talvez dentro do nosso próprio <document> personalizado ? Quaisquer sugestões / solicitações de pull são bem-vindas!

Adoraria incluir eventualmente este exemplo de olá mundo dentro da pasta Próximos exemplos e na página inicial do Apollo.

O único pré-requisito para renderizar os dados pelo servidor é que eles sejam retornados como um objeto em getInitialProps , sem necessidade de substituições.

Peguei vocês. Eu acho que isso é um pouco difícil com react-apollo porque como @nmaro apontou:

a questão é se o apollo tem uma maneira de detectar todas as chamadas de dados que serão necessárias para renderizar uma árvore de componentes e fazer tudo isso em uma chamada de função que pode ser fornecida para getInitialProps.

Peguei vocês

@ads1018 A partir de um pouco de bisbilhotar, se o componente de nível superior foi exposto em getInitialProps, ele poderia ser renderizado em string usando o auxiliar Apollo .

O _document seria então algo como:

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 Parece uma mudança simples expor o app além de renderPage , mas há algo que estou ignorando?

@bs1180 ah brilhante. Isso é o que eu estava procurando. Espero que seja uma mudança simples expor app . Isso tornaria instantaneamente o Next um framework amigável para o cliente graphql.

@bs1180 Eu expus app dentro do objeto de retorno renderPage . Isso está de acordo com o que você estava pensando?

@ads1018 Não exatamente - em sua versão render ainda está sendo chamado, o que seria uma duplicação desnecessária se renderToStringWithData fosse chamado manualmente.

Eu trabalhei um pouco mais nisso e meu resultado final não é tão bonito quanto eu imaginei, principalmente porque o aplicativo principal está sendo renderizado como um filho do componente <Main /> (no __next div), o que explode longe de qualquer contexto de ser passado para o seu aplicativo de cima. Portanto, ainda precisa de um HOC para adicionar o contexto Apollo novamente.

@bs1180 eu vejo. É possível renderizar <Main /> como um filho de ApolloProvider para que possamos passar o contexto?

Não tenho certeza do que você quer dizer, mas acho que é a direção errada. SSR perfeito pode ser alcançado com apenas um HOC - aqui está minha versão remendada como ponto de partida:

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>
    ) 
  }
}

O initClient e initStore são modelados no exemplo redux. Cada página fica assim:

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

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

Espero que seja útil - adoraria saber se existem outros caminhos para investigar ou algo que estou ignorando.

@bs1180 Legal, isso é super útil, obrigado por compartilhar.

Existe mais alguma coisa que possamos renderizar páginas com dados graphql dentro de _document.js ? Seria bom se pudéssemos ignorar esse HOC todos juntos, como você propôs inicialmente.

Acho que não - pelo que posso ver, a renderização do lado do cliente removerá qualquer coisa que esteja sendo passada no contexto (seja o cliente Apollo, a loja Redux padrão, temas etc.) do _document.js personalizado. Embora parte da lógica do Apollo SSR possa ser movida para lá, algum tipo de componente HOC/wrapper ainda será necessário para adicionar os objetos necessários de volta ao contexto.
Alguém com um melhor conhecimento dos internos do next.js pode ter uma ideia melhor.

Bem, se você conseguir obter um exemplo de trabalho, adoraria dar uma olhada. Ainda estou lutando para que isso funcione.

Eu tenho um exemplo funcional de React Apollo e Next 😄 🚀 Espero que muitos de vocês achem útil. Você pode conferir aqui: https://github.com/ads1018/next-apollo-example (também implantei uma demonstração usando o Now.)

Acabei usando um HOC dentro da minha página chamado withData() que envolve a página com ApolloProvider . Eu estava inicialmente desanimado usando provedores por página em vez de uma vez dentro de um único arquivo, mas fui convencido por algumas pessoas realmente inteligentes de que é melhor para legibilidade e escalabilidade. Eu realmente acho que withData(MyComponent) parece muito bom e fornece um bom contexto para o leitor (sem trocadilhos) que uma determinada página busca dados.

Obrigado @bs1180 e @rauchg por me apontarem na direção certa. Se você quiser adicionar um exemplo with-apollo ao repositório, me avise e eu posso criar uma solicitação pull.

Obrigado @ads1018 😊 Comparado ao meu exemplo https://Relate.now.sh , este exemplo resolve o problema de usar o Apollo em componentes profundamente aninhados (evitando a cascata de getInitialProps)? Talvez o exemplo deva mostrar isso, pois é o principal ponto de dor. E tenho certeza de que adicionar isso à pasta de exemplos seria muito apreciado.

@sedubois Não consigo reproduzir o erro que você mencionou em # 192. Estou usando o Apollo dentro de componentes aninhados sem problemas. Se você baixar meu exemplo e puder reproduzi-lo, você me avisaria?

Obrigado @ads1018 , as coisas funcionam muito bem com as correções em https://github.com/ads1018/next-apollo-example/issues/2 🎉. Atualizei meu exemplo também: https://github.com/RelateNow/relate

Belo trabalho, @ads1018 @sedubois! Eu tenho acompanhado isso e #192, também tenho investigado visualizações de pré-busca/assíncrona usando Apollo e vanilla React.

Você notou ou prevê algum problema de desempenho ao executar getDataFromTree antes de cada página ser exibida? Como tecnicamente, esse método renderiza a árvore inteira recursivamente e, quando getInitialProps retorna, o React renderiza a árvore novamente (embora com dados do cache).

Solução muito legal 👍 Acho que renderizar duas vezes é a única opção para garantir que todos os dados filhos sejam armazenados em cache, apenas curioso o que vocês pensam sobre o desempenho.

Ei @estrattonbailey - não notei nenhum problema de desempenho e não prevejo nenhum. Na verdade, é super ágil! Quanto à execução de getDataFromTree , eu envolvi essa chamada de método dentro de uma condicional que verifica se estamos no servidor para que ela seja chamada apenas quando um usuário carregar o aplicativo pela primeira vez e seja ignorada em todas as alterações de rota subsequentes . Você pode brincar com a demo se quiser conferir o desempenho. Por favor, deixe-me saber se você tem algum feedback!

@ads1018 algumas ideias para o seu exemplo:

  • simplifique inicialState assim
  • middleware separado, armazenamento e redutor em arquivos como este
  • simplifique isServer para typeof window !== 'undefined' , solte !!ctx.req
  • extraia esse IS_SERVER const para lib, não há necessidade de passá-lo como param

@ads1018 Ótimo ouvir! Bela pequena demonstração.

O que eu queria perguntar é: quão bem essa escala será? Embora eu ainda não tenha usado Next, pelo que entendi, Next chama getInitialProps em cada transição de rota, se disponível em um componente de página, ou seja pages/page.js . Em um aplicativo/site em grande escala com centenas de nós e muitos dados chegando, imagino que renderizar duas vezes em cada rota poderia contribuir para alguma latência.

O projeto em que estou trabalhando é um site editorial de grande escala, então espero fazer alguns benchmarkings de diferentes abordagens, incluindo a sua. Adoraria discutir mais no twitter se você quiser. Obrigado pelo seu trabalho!

@estrattonbailey Peguei . Imagino que vai escalar muito bem. Para o carregamento inicial da página, getInitialProps será executado apenas no servidor. Você está certo de que getInitialProps será executado novamente no cliente, mas nenhum dado será solicitado duas vezes porque getDataFromTree está dentro de uma condicional que verifica se estamos no servidor ou não.

Nota lateral - se você está preocupado com o tempo de carregamento inicial da página devido a muitos componentes e dados sendo solicitados em uma página, você sempre pode dizer ao apollo para ignorar intencionalmente consultas específicas durante o SSR e descarregá-las para o cliente passando ssr: false nas opções de consulta do Apollo.

Entrarei em contato com você no twitter se você quiser discutir mais :)

Você está certo de que getInitialProps será executado novamente no cliente, mas nenhum dado seria solicitado duas vezes porque getDataFromTree é encapsulado dentro de uma condicional que verifica se estamos no servidor ou não.

Importante ter em mente que getInitialProps é executado no lado do cliente apenas ao fazer a transição com <Link> , não após o carregamento inicial

@ads1018 @estrattonbailey AFAIK ainda existem de fato 2 renderizações no lado do servidor no carregamento da primeira página: getDataFromTree é executado e renderiza toda a árvore internamente, então a renderização é chamada novamente para construir a resposta HTML. Não pense se há alguma maneira de evitar isso, mas acho que ainda é bastante eficiente graças às viagens de ida e volta da rede evitadas pelo SSR.

Eu acho que o desempenho é máximo quando o servidor GraphQL está hospedado na mesma máquina que o servidor Next.js, então você sempre pode tentar isso se estiver preocupado com o desempenho (neste ponto, eu prototipo meu aplicativo com Graphcool para o backend, enquanto o Next.js é implantado com Now/Zeit World).

@sedubois @estrattonbailey Corrija-me se estiver errado, mas ainda estamos renderizando apenas uma vez . getDataFromTree não renderiza a árvore, ele simplesmente retorna uma promessa que resolve quando os dados estão prontos em nossa loja Apollo Client. No ponto em que a promessa é resolvida, nosso repositório do cliente Apollo será completamente inicializado e, opcionalmente , podemos renderizar a árvore se quisermos passar os resultados de string na resposta de uma solicitação HTTP, mas não estamos fazendo isso no meu exemplo.

getDataFromTree não renderiza a árvore

@ads1018 AFAIK e olhando para o código Apollo , ele _faz_ renderizar a árvore recursivamente (apenas para acionar a busca de dados Apollo). Então são 2 renderizações no lado do servidor no carregamento da primeira página.

Mas de qualquer forma, graças à sua demonstração, agora temos uma integração utilizável entre o Apollo e o Next, e as perguntas restantes sobre o desempenho do Apollo SSR não têm mais nada específico para o Next.js, eu acho. Sugiro fazer perguntas sobre isso por lá.

@sedubois o que é um render de qualquer maneira? Eu chamaria isso de andar e sacudir a árvore. Parece ser muito bem otimizado – setState suprimido e nenhuma reconciliação no DOM.

@ads1018 bom exemplo! Parece que também foi adicionado ao Wiki aqui , então esse problema provavelmente pode ser encerrado?

cc @rauchg

Devemos obter uma postagem no blog sobre Apollo + Next.js no blog Apollo!

O exemplo de @stubailo @ads1018 é ótimo 👏 Para algo maior usando os mesmos princípios do Apollo, pode verificar meu aplicativo: https://github.com/relatenow/relate

Obrigado @helfer. Estou emocionado com a forma como saiu. Sinto que descobri o santo graal do desenvolvimento de aplicativos com Next.js + Apollo. Eu tenho pensado em seguir com uma postagem no blog em um esforço para espalhar o evangelho, mas ainda não consegui. @stubailo ficaria feliz em colaborar em uma peça na publicação do meio Apollo :)

Um grande grito para @sedubois por ajudar com o exemplo e seu doce aplicativo. 😄

@ads1018 adoraria ter você no blog. Quando estiver pronto para conversar sobre isso, me ligue (thea) no Apollo Slack. :)

@helfer Você está totalmente certo. Devo fazer um novo passe de problema para ver se os problemas podem ser encerrados 😄

@stubailo @theadactyl ideia incrível ❤️

Alguém sabe de um problema / PR a ser observado - em relação à solicitação de dados duas vezes no lado do servidor? Apenas curioso

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

DvirSh picture DvirSh  ·  3Comentários

renatorib picture renatorib  ·  3Comentários

knipferrc picture knipferrc  ·  3Comentários

olifante picture olifante  ·  3Comentários

timneutkens picture timneutkens  ·  3Comentários