Next.js: Ejemplo mínimo de apolo

Creado en 13 dic. 2016  ·  60Comentarios  ·  Fuente: vercel/next.js

Resulta que la integración de apollo es mucho más fácil cuando se usa apollo-client directamente en lugar de react-apollo.

Aquí está el código: https://github.com/nmaro/apollo-next-example
Y aquí hay una versión en ejecución (al menos mientras mantenga el servidor Graphql en línea): https://apollo-next-example-oslkzaynhp.now.sh

Los detalles relevantes están aquí:

apolo.js

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

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

luego en una pagina

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

Comentario más útil

¡Deberíamos obtener una publicación de blog sobre Apollo + Next.js en el blog de Apollo!

Todos 60 comentarios

Algunas observaciones: este enfoque no profundiza en los componentes para cargar todas las consultas de graphql que encuentra (algo que puede habilitar en el lado del servidor con react-apollo).

Creo que esto es un poco problemático con next.js: en realidad no está destinado a cargar datos en lo profundo de la jerarquía de componentes, si desea que suceda tanto en el lado del cliente como en el del servidor. Solo hay un punto para cargar los datos: en getInitialProps en el componente raíz. No sé si esto va a cambiar en el futuro, pero si no, tendremos que

  1. diseñar nuestras aplicaciones para que carguemos todos los datos relevantes para una página desde el principio, o
  2. una parte de los datos solo en el cliente (es decir, todo lo que no cargamos en getInitialProps), con una estrategia diferente

En ambos casos, el enfoque anterior debería estar bien para los datos que se cargan en getInitialProps.

Y si a algún desarrollador principal le gusta esto, puedo crear una solicitud de extracción con el ejemplo.

Acerca de getInitialProps solo llamado en la raíz, consulte https://github.com/zeit/next.js/issues/192. Me encantaría tener tus ideas allí.

@sedubois , ¿qué problemas tenías con react-apollo ?

@nmaro su https://github.com/nmaro/apollo-next-example está vacío.

@amccloud es mejor que le pregunte a @nmaro sobre eso (todavía necesito volver al código).

Gracias @sedubois , ahora está en línea (siempre olvide ejecutar push origin master en lugar de solo push la primera vez).

Vaya, se lo mencioné a la persona equivocada. @nmaro , ¿qué problema tuviste con react-apollo?

Los datos se cargaron en el servidor, luego, tan pronto como el cliente comenzó a cargar, la página volvió a estar vacía. Luego miré la implementación de @sedubois (https://github.com/RelateMind/relate) y pensé que ya era bastante complejo para una prueba de concepto rápida, así que finalmente probé con la API de nivel inferior.

@stubailo ya que se preguntaba por qué es tan difícil integrar apollo con next.js: parece que el único lugar donde puede obtener datos tanto en el cliente como en el servidor es en el componente raíz de la página dentro de una función asíncrona llamada getInitialProps. Creo que la forma habitual de integrar react-apollo solo sería útil en el lado del cliente.

Interesante: ¿hay otras integraciones de datos con Next.js? Parece que usar Redux también es bastante difícil según los ejemplos que he visto.

La mayoría de los sistemas de datos modernos tienen algún tipo de caché global (Redux, Apollo, Relay), por lo que creo que debe haber algún tipo de instalación en Next para habilitar esto.

¿Cómo podemos hacer que Next.js funcione mejor con los sistemas de datos modernos con un caché global (Redux, Apollo, Relay)? Siento que esto debería ser una gran prioridad para el próximo lanzamiento. @stubailo @rauchg

Absolutamente. Tenemos un ejemplo de Redux en la wiki, necesitamos crear más como esos :)

Por cierto, no es algo que tengamos que hacer sobre la base de un lanzamiento. Podemos escribir un tutorial wiki en cualquier momento.

Por cierto, @nmaro, ese ejemplo se ve muy bien, gracias por contribuir. Podemos tomar eso como base y expandirlo.

Oh, extraño, no me di cuenta de los problemas involucrados. @nmaro, ¿qué tiene react-apollo que dificulta las cosas? parece que debería poder seguir el ejemplo de redux casi exactamente, pero haga new ApolloClient donde esto usa createStore , y use ApolloProvider en lugar de Provider .

Me encantaría trabajar con alguien para hacer un ejemplo mínimo. Este es nuestro ejemplo de "hola mundo" para React, sería genial tener un puerto para Next.js: https://github.com/apollostack/frontpage-react-app

@stubailo Me encantaría trabajar contigo en un ejemplo mínimo. He estado usando el micromarco universal de apolo, Saturno, para un par de proyectos y me encantaría trasladarlos a Next.js + Apollo en última instancia :)

Genial, sí, simplemente hacer modificaciones mínimas en la aplicación de la página principal para que se ejecute en next.js en lugar de crear-reaccionar-aplicación sería mi preferencia. ¡entonces también podemos incluirlo en nuestra página de inicio!

@stubailo

Un pequeño problema fue que los datos se cargaron y procesaron en el servidor, solo para ser reemplazados por nada cuando se cargan en el cliente. Supongo que simplemente no conozco a apollo y lo siguiente como para hacerlo bien. Al usar apollo-client directamente, no tuve este problema.

Lo que es más complicado para la representación del servidor es si tiene consultas más profundas en la jerarquía. React no tiene una forma de renderizar las cosas de forma asíncrona, es decir, esperar a que cada componente esté listo antes de renderizarlo. Lo que significa que un marco ssr tiene que

  1. recorra todo el árbol de componentes dos veces, una para cargar los datos y otra para representarlos.
  2. proporcionar un punto de entrada asíncrono en la raíz: este es el enfoque next.js con getInitialProps

Ahora la pregunta es si apollo tiene una forma de detectar todas las llamadas de datos que se necesitarán para representar un árbol de componentes, y hacer todo esto en una llamada de función que se puede proporcionar a getInitialProps.

@stubailo ¿Hay una solución para esto? ^

@nmaro @ads1018 ¿has visto getDataFromTree ? Como se usa, por ejemplo, en mi ejemplo: https://github.com/RelateMind/relate/blob/master/hocs/apollo.js

Por cierto, me pregunto si las cosas se pueden simplificar ahora que https://github.com/zeit/next.js/pull/301 se fusionó. No he investigado eso todavía.

@sedubois Lo comprobé gracias por compartir! Sí, me imagino que su ejemplo usando react-apollo se puede simplificar con la nueva API programática (# 301) que se fusionó con Master para que no tenga que envolver todos los componentes de la página con su propio HOC. Si hace algún progreso en eso, por favor hágamelo saber! Sería genial obtener un ejemplo de next.js en la página de inicio de apollo :)

NB @ads1018 https://github.com/zeit/next.js/pull/301 se trata de extraer código común con CommonsChunkPlugin, no API programática. Pero sí, la API programática definitivamente también ayudará, espero que se lance.

¿Alguien ha tenido suerte haciendo que react-apollo funcione con la nueva versión 2.0.0-beta.2?

@sedubois @stubailo Aumenté mi intento de next + react-apollo si quieres echar un vistazo. Puede encontrarlo aquí: https://github.com/ads1018/frontpage-next-app

Un problema al que me enfrento en este momento es que los componentes solo se procesan en el lado del cliente y no en el lado del servidor. ¿Quizás podamos usar el método getDataFromTree de react-apollo dentro de server.js? ¿O tal vez dentro de nuestro propio <document> personalizado ? ¡Cualquier sugerencia / solicitud de extracción es bienvenida!

Me encantaría incluir eventualmente este ejemplo de hola mundo dentro de la carpeta Siguientes ejemplos y la página de inicio de Apollo.

El único requisito previo para la representación de los datos en el servidor es que se devuelvan como un objeto en getInitialProps , sin necesidad de anulaciones.

Entendido. Creo que esto es un poco difícil con react-apollo porque, como señaló @nmaro :

la pregunta es si apollo tiene una forma de detectar todas las llamadas de datos que se necesitarán para representar un árbol de componentes, y hacer todo esto en una llamada de función que se puede proporcionar a getInitialProps.

Entendido

@ads1018 Después de hurgar un poco, si el componente de nivel superior se expuso en getInitialProps, podría convertirse en una cadena con el ayudante de Apollo .

El _document sería entonces 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 un cambio simple exponer el app además de renderPage , pero ¿hay algo que esté pasando por alto?

@bs1180 ah brillante. Eso es lo que estaba buscando. Con suerte, es un cambio simple exponer app . Instantáneamente convertiría a Next en un framework amigable con el cliente de graphql.

@bs1180 He expuesto app dentro del objeto de retorno renderPage . ¿Esto se alinea con lo que estabas pensando?

@ads1018 No del todo: en su versión render todavía se está llamando, lo que sería una duplicación innecesaria si renderToStringWithData se va a llamar manualmente.

Trabajé un poco más en esto y mi resultado final no es tan bonito como imaginé al principio, principalmente porque la aplicación principal se representa como un elemento secundario del componente <Main /> (en el __siguiente div), que explota evita que cualquier contexto se transmita a su aplicación desde arriba. Por lo tanto, todavía necesita un HOC para agregar el contexto de Apolo nuevamente.

@ bs1180 Ya veo. ¿Es posible representar <Main /> como un elemento secundario de ApolloProvider para que podamos transmitir el contexto?

No estoy seguro de lo que quieres decir, pero creo que es la dirección equivocada. La SSR perfecta se puede lograr con solo un HOC; aquí está mi versión improvisada como punto 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>
    ) 
  }
}

El initClient y initStore están modelados en el ejemplo de redux. Cada página entonces se ve así:

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

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

Espero que sea útil: me encantaría saber si hay otras vías para investigar, o algo que estoy pasando por alto.

@bs1180 Genial, esto es muy útil, gracias por compartir.

¿Hay algo más que podamos hacer para representar páginas con datos de graphql dentro _document.js ? Sería bueno si pudiéramos pasar por alto ese HOC todos juntos como propusieron inicialmente.

No lo creo: por lo que puedo ver, el renderizado del lado del cliente eliminará cualquier cosa que se pase en el contexto (ya sea el cliente Apollo, la tienda Redux estándar, temas, etc.) del _document.js personalizado. Aunque parte de la lógica Apollo SSR podría moverse allí, aún será necesario algún tipo de componente HOC/envoltura para agregar los objetos necesarios nuevamente al contexto.
Sin embargo, alguien con un mejor conocimiento de las funciones internas de next.js podría tener una mejor idea.

Bueno, si logras obtener un ejemplo de trabajo, me encantaría comprobarlo. Todavía estoy luchando para que esto funcione.

Tengo un ejemplo funcional de React Apollo y Next 😄 🚀 Espero que a muchos de ustedes les resulte útil. Puede consultarlo aquí: https://github.com/ads1018/next-apollo-example (también implementé una demostración usando Now).

Terminé usando un HOC dentro de mi página llamado withData() que envuelve la página con ApolloProvider . Inicialmente me desconcertó el uso de proveedores por página en lugar de una vez dentro de un solo archivo, pero algunas personas realmente inteligentes me convencieron de que es mejor para la legibilidad y la escalabilidad. De hecho, creo que withData(MyComponent) se ve bastante bien y proporciona un buen contexto para el lector (sin juego de palabras) de que una página en particular obtiene datos.

Gracias @bs1180 y @rauchg por orientarme en la dirección correcta. Si desea agregar un ejemplo de with-apollo al repositorio, hágamelo saber y puedo crear una solicitud de extracción.

Gracias @ads1018 😊 En comparación con mi ejemplo https://Relate.now.sh , ¿este ejemplo resuelve el problema de usar Apollo en componentes profundamente anidados (evitando la cascada de getInitialProps)? Tal vez el ejemplo debería mostrar eso, ya que es el principal punto débil. Y estoy seguro de que sería muy apreciado agregar esto a la carpeta de ejemplos.

@sedubois No puedo reproducir el error al que se hace referencia en el n.º 192. Estoy usando Apollo dentro de componentes anidados sin ningún problema. Si despliegas mi ejemplo y puedes reproducirlo, ¿me lo harías saber?

Gracias @ads1018 , todo funciona muy bien con las correcciones en https://github.com/ads1018/next-apollo-example/issues/2 🎉. También actualicé mi ejemplo: https://github.com/RelateNow/relate

¡Buen trabajo, @ads1018 @sedubois! He estado siguiendo esto y el n. ° 192, también he estado investigando vistas precargadas/asincrónicas usando Apollo y Vanilla React.

¿Ha notado o prevé algún problema de rendimiento al ejecutar getDataFromTree antes de que se muestre cada página? Dado que técnicamente, ese método representa todo el árbol de forma recursiva , y luego, cuando getInitialProps regresa, React representa el árbol nuevamente (aunque con datos del caché).

Muy buena solución 👍 Creo que renderizar dos veces es la única opción para garantizar que todos los datos de los niños se almacenen en caché, solo tengo curiosidad por saber qué piensan sobre el rendimiento.

Hola @estrattonbailey : no he notado ningún problema de rendimiento y no anticipo ninguno. De hecho, ¡es súper ágil! En cuanto a ejecutar getDataFromTree , envolví esa llamada de método dentro de un condicional que verifica si estamos en el servidor, por lo que solo se llama cuando un usuario carga la aplicación por primera vez y se omite en todos los cambios de ruta posteriores . Puedes jugar con la demostración si quieres ver el rendimiento. Por favor, hágamelo saber si tiene algún comentario.

@ads1018 algunas ideas para su ejemplo:

  • simplificar initialState como este
  • separe el middleware, la tienda y el reductor en archivos como este
  • simplifique isServer a typeof window !== 'undefined' , suelte !!ctx.req
  • extraiga esa const IS_SERVER a lib, no es necesario pasarla como parámetro

@ads1018 ¡Qué bueno escucharlo! Bonita pequeña demostración.

Lo que quise preguntar es: ¿qué tan bien escalará esta escala? Aunque todavía no he usado Next, según tengo entendido, Next llama a getInitialProps en cada transición de ruta, si está disponible en un componente de página, es decir pages/page.js . En una aplicación/sitio web a gran escala con cientos de nodos y una gran cantidad de datos, imagino que la representación dos veces en cada ruta podría contribuir a cierta latencia.

El proyecto en el que estoy trabajando es un sitio editorial a gran escala, así que espero hacer algunas evaluaciones comparativas de diferentes enfoques, incluido el suyo. Me encantaría discutir más en Twitter si lo desea. ¡Gracias por tu trabajo!

@estrattonbailey Te tengo . Imagino que escalará muy bien. Para la carga de la página inicial, getInitialProps se ejecutará solo en el servidor. Tiene razón en que getInitialProps se ejecutará nuevamente en el cliente, pero no se solicitarán datos dos veces porque getDataFromTree está envuelto dentro de un condicional que verifica si estamos en el servidor o no.

Nota al margen: si le preocupa el tiempo de carga inicial de la página debido a que se solicitan muchos componentes y datos en una página, siempre puede decirle a apollo que omita intencionalmente consultas específicas durante SSR y las descargue al cliente pasando ssr: false en las opciones de consulta de apolo.

Me conectaré contigo en twitter si quieres discutir más :)

Tiene razón en que getInitialProps se ejecutará nuevamente en el cliente, pero no se solicitarán datos dos veces porque getDataFromTree está envuelto dentro de un condicional que verifica si estamos en el servidor o no.

Es importante tener en cuenta que getInitialProps se ejecuta en el lado del cliente solo cuando se realiza la transición con <Link> , no después de la carga inicial

@ads1018 @estrattonbailey AFAIK, todavía hay 2 renderizaciones del lado del servidor en la carga de la primera página: getDataFromTree se ejecuta y renderiza todo el árbol internamente, luego se vuelve a llamar para construir la respuesta HTML. No piense si hay alguna forma de evitar eso, pero supongo que todavía tiene un buen rendimiento gracias a los viajes de ida y vuelta de la red evitados por SSR.

Supongo que el rendimiento es máximo cuando el servidor GraphQL está alojado en la misma máquina que el servidor Next.js, por lo que siempre puede intentarlo si le preocupa el rendimiento (en este punto, prototifico mi aplicación con Graphcool para el backend, mientras que Next.js se implementa con Now/Zeit World).

@sedubois @estrattonbailey Corríjame si me equivoco, pero solo estamos renderizando una vez . getDataFromTree no representa el árbol, simplemente devuelve una promesa que se resuelve cuando los datos están listos en nuestra tienda Apollo Client. En el momento en que se resuelve la promesa, nuestra tienda Apollo Client se inicializará por completo y, opcionalmente , podemos representar el árbol si queremos pasar los resultados en cadena en la respuesta de una solicitud HTTP, pero no lo haremos en mi ejemplo.

getDataFromTree no representa el árbol

@ ads1018 AFAIK y mirando el código de Apollo , _sí_ representa el árbol de forma recursiva (solo para activar la obtención de datos de Apollo). Eso es 2 renderizados del lado del servidor en la carga de la primera página.

Pero de todos modos, gracias a su demostración, ahora tenemos una integración utilizable entre Apollo y Next, y creo que las preguntas restantes sobre el rendimiento de Apollo SSR ya no tienen nada específico para Next.js. Sugiero hacer preguntas sobre eso allí.

@sedubois, ¿qué es un render de todos modos? Yo lo llamaría caminar y sacudir el árbol. Parece estar bastante bien optimizado: suprimido setState y sin reconciliación en DOM.

@ads1018 buen ejemplo! Parece que también se ha agregado a Wiki aquí , por lo que este problema probablemente podría cerrarse.

cc @rauchg

¡Deberíamos obtener una publicación de blog sobre Apollo + Next.js en el blog de Apollo!

El ejemplo de @stubailo @ads1018 es excelente 👏 Para algo más grande que use los mismos principios de Apollo, puede consultar mi aplicación: https://github.com/relatenow/relate

Gracias @helfer. Estoy encantada con cómo salió. Siento que descubrí el santo grial del desarrollo de aplicaciones con Next.js + Apollo. Tenía la intención de hacer un seguimiento con una publicación de blog en un esfuerzo por difundir el evangelio, pero simplemente no lo he logrado. @stubailo Me encantaría colaborar en una pieza en la publicación del medio Apollo :)

Un gran agradecimiento a @sedubois por ayudar con el ejemplo y su dulce aplicación. 😄

A @ads1018 le encantaría incluirte en el blog. Cuando esté listo para conversar sobre el tema, envíeme un ping (thea) en Apollo Slack. :)

@helfer Tienes toda la razón. Debería hacer un nuevo pase de problema para ver si los problemas se pueden cerrar 😄

@stubailo @theadactyl genial idea ❤️

¿Alguien sabe de un problema/relaciones públicas a tener en cuenta, con respecto a la solicitud de datos dos veces en el lado del servidor? Sólo curioso

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

irrigator picture irrigator  ·  3Comentarios

kenji4569 picture kenji4569  ·  3Comentarios

wagerfield picture wagerfield  ·  3Comentarios

havefive picture havefive  ·  3Comentarios

sospedra picture sospedra  ·  3Comentarios