Next.js: Exemple d'apollo minimal

Créé le 13 déc. 2016  ·  60Commentaires  ·  Source: vercel/next.js

Il s'avère que l'intégration d'apollo est beaucoup plus facile lorsque vous utilisez apollo-client directement au lieu de react-apollo.

Voici le code : https://github.com/nmaro/apollo-next-example
Et voici une version en cours d'exécution (au moins tant que je garde le serveur graphql en ligne): https://apollo-next-example-oslkzaynhp.now.sh

Les détails pertinents sont ici :

apollo.js

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

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

puis dans une page

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

Commentaire le plus utile

Nous devrions avoir un article de blog sur Apollo + Next.js sur le blog Apollo !

Tous les 60 commentaires

Quelques observations : cette approche ne va pas en profondeur dans les composants pour charger toutes les requêtes graphql qu'elle rencontre (quelque chose que vous pouvez activer côté serveur avec react-apollo).

Je pense que c'est un peu problématique avec next.js : vous n'êtes pas vraiment censé charger des données profondément dans la hiérarchie des composants - si vous voulez que cela se produise à la fois côté client et côté serveur. Il n'y a qu'un seul point pour charger les données : dans getInitialProps dans le composant racine. Je ne sais pas si cela va changer à l'avenir, mais sinon, nous devrons

  1. architecturer nos applications afin que nous chargions toutes les données pertinentes pour une page dès le début, ou
  2. une partie des données uniquement sur le client (à savoir tout ce que nous ne chargeons pas dans getInitialProps), avec une stratégie différente

Dans les deux cas, l'approche ci-dessus devrait convenir aux données chargées dans getInitialProps.

Et si un développeur principal aime cela, je peux créer une demande d'extraction avec l'exemple.

À propos de getInitialProps appelé uniquement à la racine, voir https://github.com/zeit/next.js/issues/192. J'aimerais y avoir vos idées.

@sedubois quels problèmes rencontriez-vous avec react-apollo ?

@nmaro votre https://github.com/nmaro/apollo-next-example est vide.

@amccloud ferait mieux de demander à @nmaro à ce sujet (je dois encore revenir dans le code).

Merci @sedubois c'est maintenant en ligne (oublie toujours de lancer push origin master au lieu de seulement push la première fois).

Oups, j'ai mentionné à la mauvaise personne. @nmaro quel problème avez-vous eu avec react-apollo ?

Les données ont été chargées dans le serveur, puis dès que le client a commencé à charger la page était à nouveau vide. J'ai ensuite regardé l'implémentation de @sedubois (https://github.com/RelateMind/relate), et j'ai pensé que c'était déjà assez complexe pour une preuve de concept rapide, alors j'ai finalement essayé avec l'API de niveau inférieur.

@stubailo puisque vous vous demandiez pourquoi il est si difficile d'intégrer apollo avec next.js - il semble que le seul endroit où vous pouvez récupérer des données à la fois sur le client et sur le serveur se trouve au composant racine de la page dans une fonction asynchrone appelée getInitialProps. Je pense que la manière habituelle d'intégrer react-apollo ne serait utile que du côté client.

Intéressant - existe-t-il d'autres intégrations de données avec Next.js ? Il semble que l'utilisation de Redux soit également assez difficile d'après les exemples que j'ai vus.

La plupart des systèmes de données modernes ont une sorte de cache global (Redux, Apollo, Relay) donc j'ai l'impression qu'il doit y avoir une sorte d'installation dans Next pour permettre cela.

Comment pouvons-nous rendre Next.js plus agréable avec les systèmes de données modernes avec un cache global (Redux, Apollo, Relay) ? Je pense que cela devrait être une grande priorité pour la prochaine version. @stubailo @rauchg

Absolument. Nous avons un exemple Redux sur le wiki, nous devons en créer d'autres comme ceux-là :)

Ce n'est pas quelque chose que nous devons faire sur une base de publication d'ailleurs. Nous pouvons simplement écrire un tutoriel wiki à tout moment.

Btw @nmaro cet exemple a l'air vraiment sympa, merci d'avoir contribué. Nous pouvons prendre cela comme base et l'étendre

Oh, bizarre - je n'avais pas réalisé les problèmes impliqués. @nmaro qu'est-ce qui rend les choses difficiles à propos de react-apollo ? semble que vous devriez être capable de suivre l' exemple redux presque exactement mais faites new ApolloClient où cela utilise createStore , et utilisez ApolloProvider au lieu de Provider .

J'aimerais travailler avec quelqu'un pour faire un exemple minimal. Ceci est notre exemple "hello world" pour React, ce serait formidable d'avoir un port pour Next.js : https://github.com/apollostack/frontpage-react-app

@stubailo J'aimerais travailler avec vous sur un exemple minimal. J'utilise le microframework apollo universel, Saturn, pour quelques projets et j'aimerais les porter sur Next.js + Apollo finalement :)

Nice - ouais, apporter des modifications minimes à l'application frontpage pour la faire fonctionner sur next.js au lieu de create-react-app serait ma préférence. alors nous pouvons également le lister sur notre page d'accueil!

@stubailo

Un petit problème était que les données étaient chargées et rendues sur le serveur, pour être remplacées par rien lors du chargement sur le client - je suppose que je ne connais pas apollo et assez ensuite pour bien faire les choses. En utilisant apollo-client directement, je n'ai pas eu ce problème.

Ce qui est plus délicat pour le rendu du serveur, c'est si vous avez des requêtes plus profondes dans la hiérarchie. React n'a pas de moyen de rendre les choses de manière asynchrone, c'est-à-dire d'attendre que chaque composant soit prêt avant de le rendre. Ce qui signifie qu'un framework ssr doit soit

  1. parcourir l'ensemble de l'arborescence des composants deux fois, une fois pour charger les données et une fois pour les afficher.
  2. fournir un point d'entrée asynchrone à la racine - c'est l'approche next.js avec getInitialProps

Maintenant, la question est de savoir si apollo a un moyen de détecter tous les appels de données qui seront nécessaires pour rendre une arborescence de composants, et de faire tout cela dans un seul appel de fonction qui peut être fourni à getInitialProps.

@stubailo Existe-t-il une solution à cela? ^

@nmaro @ads1018 avez-vous vu getDataFromTree ? Tel qu'utilisé par exemple dans mon exemple : https://github.com/RelateMind/relate/blob/master/hocs/apollo.js

BTW, je me demande si les choses peuvent être simplifiées maintenant que https://github.com/zeit/next.js/pull/301 est fusionné. Je n'ai pas encore examiné cela.

@sedubois j'ai vérifié ça merci pour le partage! Ouais, j'imagine que votre exemple utilisant react-apollo peut être simplifié avec la nouvelle API programmatique (#301) qui vient d'être fusionnée dans Master afin que vous n'ayez pas à envelopper tous les composants de la page avec votre propre HOC. Si vous faites des progrès là-dessus, n'hésitez pas à me le faire savoir ! Ce serait cool d'avoir un exemple next.js sur la page d'accueil d'apollo :)

NB @ads1018 https://github.com/zeit/next.js/pull/301 concerne l'extraction de code commun avec CommonsChunkPlugin, pas l'API programmatique. Mais oui, l'API programmatique aidera certainement aussi, j'ai hâte de la publier.

Quelqu'un a-t-il réussi à faire fonctionner react-apollo avec la nouvelle version 2.0.0-beta.2 ?

@sedubois @stubailo j'ai poussé ma tentative de next + react-apollo si tu veux jeter un oeil. Vous pouvez le trouver ici : https://github.com/ads1018/frontpage-next-app

Un problème auquel je suis confronté en ce moment est que les composants ne sont rendus que côté client et non côté serveur. Peut-être pourrions-nous utiliser la méthode getDataFromTree react-apollo dans server.js ? Ou peut-être à l'intérieur de notre propre <document> ? Toutes les suggestions/demandes de tirage sont les bienvenues !

J'adorerais éventuellement inclure cet exemple hello world dans le dossier Next examples et la page d'accueil d'Apollo.

La seule condition préalable au rendu des données par le serveur est qu'elles soient renvoyées en tant qu'objet dans getInitialProps , pas besoin de remplacements.

Je t'ai eu. Je pense que c'est un peu difficile avec react-apollo car comme l'a souligné @nmaro :

la question est de savoir si apollo a un moyen de détecter tous les appels de données qui seront nécessaires pour rendre une arborescence de composants, et de faire tout cela dans un seul appel de fonction qui peut être fourni à getInitialProps.

Je t'ai eu

@ ads1018 À partir d'un peu de fouinage, si le composant de niveau supérieur était exposé sur getInitialProps, il pourrait alors être rendu en chaîne à l'aide de l' Apollo helper .

Le _document serait alors quelque chose comme :

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 Cela semble un simple changement pour exposer le app en plus de renderPage , mais y a-t-il quelque chose que je néglige?

@ bs1180 ah génial. C'est ce que je cherchais. J'espère que c'est un simple changement pour exposer app . Cela ferait instantanément de Next un framework convivial pour graphql.

@ bs1180 J'ai exposé app à l'intérieur de l' objet return renderPage . Est-ce que cela correspond à ce que vous pensiez ?

@ ads1018 Pas tout à fait - dans votre version, render est toujours appelé, ce qui serait une duplication inutile si renderToStringWithData devait être appelé manuellement.

J'ai travaillé un peu plus là-dessus et mon résultat final n'est pas aussi joli que je l'imaginais au départ, principalement parce que l'application principale est rendue en tant qu'enfant du composant <Main /> (dans la div __next), qui souffle empêcher tout contexte d'être transmis à votre application par le haut. Il a donc encore besoin d'un HOC pour ajouter à nouveau le contexte Apollo.

@ bs1180 je vois. Est-il possible de rendre <Main /> en tant qu'enfant de ApolloProvider afin que nous puissions transmettre le contexte ?

Je ne sais pas ce que vous voulez dire, mais je pense que c'est la mauvaise direction. Un SSR parfait peut être atteint avec juste un HOC - voici ma version bricolée comme point de départ :

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

Les initClient et initStore sont modélisés sur l'exemple redux. Chaque page ressemble alors à ceci :

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

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

J'espère que c'est utile - j'aimerais savoir s'il existe d'autres pistes à explorer, ou quelque chose que je néglige.

@ bs1180 Cool, c'est super utile merci pour le partage.

Y a-t-il autre chose que nous puissions rendre des pages avec des données graphql à l'intérieur _document.js ? Ce serait bien si nous pouvions contourner ce HOC tous ensemble comme vous l'avez initialement proposé.

Je ne pense pas - d'après ce que je peux voir, le rendu côté client supprimera tout ce qui est transmis sur le contexte (que ce soit le client Apollo, le magasin Redux standard, les thèmes, etc.) du _document.js personnalisé. Bien qu'une partie de la logique Apollo SSR puisse y être déplacée, une sorte de composant HOC/wrapper sera toujours nécessaire pour ajouter les objets nécessaires au contexte.
Quelqu'un ayant une meilleure connaissance des composants internes de next.js pourrait cependant avoir une meilleure idée.

Eh bien, si vous parvenez à obtenir un exemple de travail, j'aimerais le vérifier. Je me bats toujours pour que ça marche.

J'ai un exemple fonctionnel de React Apollo et Next 😄 🚀 J'espère que beaucoup d'entre vous le trouveront utile. Vous pouvez le vérifier ici : https://github.com/ads1018/next-apollo-example (j'ai également déployé une démo à l'aide de Now.)

J'ai fini par utiliser un HOC dans ma page appelé withData() qui enveloppe la page avec ApolloProvider . J'ai d'abord été rebuté par l'utilisation de fournisseurs sur une base par page plutôt qu'une fois dans un seul fichier, mais j'ai été convaincu par des personnes vraiment intelligentes que c'est mieux pour la lisibilité et l'évolutivité. En fait, je pense que withData(MyComponent) a l'air plutôt sympa et fournit un bon contexte au lecteur (sans jeu de mots) qu'une page particulière récupère des données.

Merci @bs1180 et @rauchg de m'avoir orienté dans la bonne direction. Si vous souhaitez ajouter un exemple with-apollo au référentiel, faites-le moi savoir et je pourrai créer une demande d'extraction.

Merci @ads1018 😊 Par rapport à mon exemple https://Relate.now.sh , cet exemple résout-il le problème de l'utilisation d'Apollo dans des composants profondément imbriqués (en évitant la cascade de getInitialProps) ? Peut-être que l'exemple devrait montrer cela car c'est le principal problème. Et je suis sûr que l'ajout de ceci au dossier des exemples serait très apprécié.

@sedubois Je n'arrive pas à reproduire l'erreur que vous avez référencée au #192. J'utilise Apollo dans des composants imbriqués sans aucun problème. Si vous réduisez mon exemple et que vous êtes capable de le reproduire, me le ferez-vous savoir ?

Merci @ads1018 , les choses fonctionnent très bien avec les correctifs dans https://github.com/ads1018/next-apollo-example/issues/2 🎉. J'ai également mis à jour mon exemple : https://github.com/RelateNow/relate

Beau travail, @ads1018 @sedubois ! J'ai suivi ce sujet et # 192, j'ai également enquêté sur les vues de prélecture/asynchrones en utilisant Apollo et vanilla React.

Avez-vous remarqué ou prévoyez-vous des problèmes de performances liés à l'exécution getDataFromTree avant l'affichage de chaque page ? Puisque techniquement, cette méthode rend l' arbre entier de manière récursive , puis lorsque getInitialProps revient, React restitue l'arbre à nouveau (bien qu'avec les données du cache).

Vraiment une belle solution 👍 Je pense que le rendu deux fois est la seule option pour s'assurer que toutes les données enfants sont mises en cache, juste curieux de savoir ce que vous pensez des performances.

Hey @estrattonbailey - Je n'ai remarqué aucun problème de performances et je n'en anticipe aucun. En fait, c'est super accrocheur ! En ce qui concerne l'exécution getDataFromTree , j'ai enveloppé cet appel de méthode dans une condition qui vérifie si nous sommes sur le serveur afin qu'il ne soit appelé que lorsqu'un utilisateur charge l'application pour la première fois et est ignoré lors de tous les changements de route ultérieurs. . Vous pouvez jouer avec la démo si vous voulez vérifier la performance. S'il vous plaît laissez-moi savoir si vous avez des commentaires!

@ads1018 quelques idées pour votre exemple :

  • simplifier initialState comme ceci
  • middleware séparé, magasin et réducteur dans des fichiers comme celui -ci
  • simplifiez isServer en typeof window !== 'undefined' , supprimez !!ctx.req
  • extraire ce IS_SERVER const à lib, pas besoin de le passer en tant que param

@ads1018 Génial à entendre ! Petite démo sympa.

Ce que je voulais demander, c'est : dans quelle mesure cette échelle fonctionnera-t-elle ? Bien que je n'aie pas encore utilisé Next, si je comprends bien, Next appelle getInitialProps sur chaque transition de route, si disponible sur un composant de page, c'est-à-dire pages/page.js . Sur une application/un site Web à grande échelle avec des centaines de nœuds et de nombreuses données entrantes, j'imagine que le rendu deux fois sur chaque route pourrait contribuer à une certaine latence.

Le projet sur lequel je travaille est un site éditorial à grande échelle, j'espère donc faire un benchmark de différentes approches, y compris la vôtre. J'aimerais discuter plus sur twitter si vous le souhaitez. Merci pour votre travail !

@estrattonbailey Gotcha. J'imagine que ça évoluera très bien. Pour le chargement initial de la page, getInitialProps s'exécutera uniquement sur le serveur. Vous avez raison de dire que getInitialProps sera exécuté à nouveau sur le client, mais aucune donnée ne sera demandée deux fois car getDataFromTree est enveloppé dans une condition qui vérifie si nous sommes sur le serveur ou non.

Note latérale - si vous êtes préoccupé par le temps de chargement initial de la page en raison de nombreux composants et données demandés sur une page, vous pouvez toujours dire à apollo d'ignorer intentionnellement des requêtes spécifiques pendant le SSR et de les décharger sur le client en passant ssr: false dans les options de requête apollo.

Je vous contacterai sur twitter si vous souhaitez en discuter davantage :)

Vous avez raison de dire que getInitialProps sera exécuté à nouveau sur le client, mais aucune donnée ne sera demandée deux fois car getDataFromTree est enveloppé dans une condition qui vérifie si nous sommes sur le serveur ou non.

Il est important de garder à l'esprit getInitialProps est exécuté côté client uniquement lors de la transition avec <Link> , pas après le chargement initial

@ ads1018 @estrattonbailey AFAIK, il y a encore 2 rendus côté serveur lors du chargement de la première page : getDataFromTree est exécuté et restitue l'intégralité de l'arborescence en interne, puis le rendu est appelé à nouveau pour construire la réponse HTML. Je ne pense pas qu'il y ait un moyen d'éviter cela, mais je suppose qu'il est toujours assez performant grâce aux allers-retours réseau évités par SSR.

Je suppose que les performances sont maximales lorsque le serveur GraphQL est hébergé sur la même machine que le serveur Next.js, vous pouvez donc toujours essayer cela si vous êtes préoccupé par les performances (à ce stade, je prototype mon application avec Graphcool pour le backend, tandis que Next.js est déployé avec Now/Zeit World).

@sedubois @estrattonbailey Corrigez-moi si je me trompe, mais nous ne rendons toujours qu'une seule fois . getDataFromTree ne rend pas l'arborescence, il renvoie simplement une promesse qui se résout lorsque les données sont prêtes dans notre magasin Apollo Client. Au moment où la promesse se résout, notre magasin Apollo Client sera complètement initialisé et nous pouvons éventuellement rendre l'arborescence si nous voulons transmettre les résultats sous forme de chaîne dans la réponse d'une requête HTTP, mais nous ne le faisons pas dans mon exemple.

getDataFromTree ne rend pas l'arbre

@ads1018 AFAIK et en regardant le code Apollo , il rend l'arbre de manière récursive (juste pour déclencher la récupération des données Apollo). C'est donc 2 rendus côté serveur lors du chargement de la première page.

Mais quoi qu'il en soit, grâce à votre démo, nous avons maintenant une intégration utilisable entre Apollo et Next, et les questions restantes sur les performances d'Apollo SSR n'ont plus rien de spécifique à Next.js, je pense. Je suggérerais de poser des questions à ce sujet là-bas.

@sedubois qu'est-ce qu'un rendu de toute façon ? J'appellerais ça marcher et secouer l'arbre. Semble être assez bien optimisé - supprimé setState et pas de réconciliation dans DOM.

@ads1018 bel exemple ! On dirait qu'il a également été ajouté au Wiki ici , donc ce problème pourrait probablement être clos ?

cc @rauchg

Nous devrions avoir un article de blog sur Apollo + Next.js sur le blog Apollo !

L'exemple de @stubailo @ads1018 est génial 👏 Pour quelque chose de plus grand utilisant les mêmes principes Apollo, vous pouvez consulter mon application : https://github.com/relatenow/relate

Merci @helfer. Je suis ravi de la façon dont il est sorti. J'ai l'impression d'avoir découvert le Saint Graal du développement d'applications avec Next.js + Apollo. J'avais l'intention de faire un suivi avec un article de blog dans le but de répandre l'évangile, mais je n'y suis tout simplement pas parvenu. @stubailo Je serais heureux de collaborer à un article sur la publication du médium Apollo :)

Un grand merci à @sedubois pour son aide avec l'exemple et sa douce application. 😄

@ads1018 aimerait vous avoir sur le blog. Lorsque vous êtes prêt à en discuter, envoyez-moi un ping (thea) sur Apollo Slack. :)

@helfer Vous avez tout à fait raison. Je devrais faire une nouvelle passe de problème pour voir si les problèmes peuvent être fermés 😄

@stubailo @theadactyl super idée ❤️

Quelqu'un connaît-il un problème / PR à surveiller - concernant la demande de données deux fois côté serveur? Juste curieux

Cette page vous a été utile?
0 / 5 - 0 notes