Next.js: Минимальный пример аполлона

Созданный на 13 дек. 2016  ·  60Комментарии  ·  Источник: vercel/next.js

Оказывается, интеграция с apollo намного проще при непосредственном использовании apollo-client вместо react-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 + Next.js в блоге Apollo!

Все 60 Комментарий

Некоторые наблюдения: этот подход не углубляется в компоненты для загрузки всех запросов graphql, с которыми он сталкивается (что-то, что вы можете включить на стороне сервера с помощью react-apollo).

Я считаю, что это немного проблематично с next.js: на самом деле вы не должны загружать данные глубоко в иерархию компонентов - если вы хотите, чтобы это происходило как на стороне клиента, так и на стороне сервера. Есть только одна точка для загрузки данных: в getInitialProps в корневом компоненте. Я не знаю, изменится ли это в будущем, но если нет, то нам придется

  1. спроектировать наши приложения таким образом, чтобы мы загружали все соответствующие данные для страницы с самого начала, или
  2. часть данных только на клиенте (а именно все, что мы не загружаем в getInitialProps), с другой стратегией

В обоих случаях описанный выше подход должен подойти для данных, загружаемых в getInitialProps.

И если это понравится какому-то основному разработчику, я могу создать запрос на вытягивание с примером.

О getInitialProps, вызываемом только в корне, см . 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, какая у тебя проблема с реакцией-аполлоном?

Данные были загружены на сервер, затем, как только клиент начал загрузку, страница снова была пустой. Затем я посмотрел на реализацию @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

Абсолютно. У нас есть пример Redux на вики, нам нужно создать больше таких :)

Кстати, это не то, что мы должны делать на основе релиза. Мы можем просто написать вики-руководство в любое время.

Кстати, @nmaro этот пример выглядит очень аккуратно, спасибо за участие. Мы можем взять это за основу и расширить.

О, странно - я не понимал проблем, связанных с этим. @nmaro , что такого в реакции-аполлоне, что все усложняет? похоже, вы должны почти точно следовать примеру с редукцией , но делать new ApolloClient , где используется createStore , и использовать ApolloProvider вместо Provider .

Я хотел бы работать с кем-то, чтобы сделать минимальный пример. Это наш пример «hello world» для React, было бы здорово иметь порт для Next.js: https://github.com/apollostack/frontpage-react-app

@stubailo Я бы хотел поработать с вами над минимальным примером. Я использовал универсальный микрофреймворк apollo, Saturn, для пары проектов и хотел бы в конечном итоге перенести их на Next.js + Apollo :)

Хорошо — да, я предпочел бы просто внести минимальные изменения в приложение на главной странице, чтобы оно работало на next.js, а не на create-react-app. тогда мы можем перечислить его и на нашей домашней странице!

@стубайло

Небольшая проблема заключалась в том, что данные загружались и отображались на сервере, но при загрузке на клиенте их заменяли ничем — я думаю, я просто не знаю аполлона и следующего достаточно, чтобы понять это правильно. Используя apollo-client напрямую, у меня не было этой проблемы.

Что более сложно для серверного рендеринга, так это если у вас есть запросы глубже в иерархии. В React нет способа асинхронного рендеринга, то есть ожидания готовности каждого компонента перед его рендерингом. Это означает, что структура ssr либо должна

  1. пройтись по всему дереву компонентов дважды, один раз для загрузки данных и один раз для их рендеринга.
  2. предоставить асинхронную точку входа в корень - это подход next.js с getInitialProps

Теперь вопрос заключается в том, есть ли у 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 можно упростить с помощью нового программного API (#301), который был только что объединен с Master, так что вам не нужно обертывать все компоненты страницы своим собственным HOC. Если вы сделаете какой-либо прогресс в этом, пожалуйста, дайте мне знать! Было бы здорово получить пример next.js на домашней странице apollo :)

NB @ads1018 https://github.com/zeit/next.js/pull/301 предназначен для извлечения общего кода с помощью CommonsChunkPlugin, а не Programmatic API. Но да, программный API определенно поможет, с нетерпением жду его выпуска.

Удалось ли кому-нибудь заставить react-apollo работать с новой версией 2.0.0-beta.2?

@sedubois @stubailo Я увеличил свою попытку в следующем + реагировать-аполло, если вы хотите взглянуть. Вы можете найти его здесь: https://github.com/ads1018/frontpage-next-app

Одна проблема, с которой я сталкиваюсь прямо сейчас, заключается в том, что компоненты отображаются только на стороне клиента, а не на стороне сервера. Возможно, мы можем использовать метод react-apollo getDataFromTree внутри server.js? Или, может быть, внутри нашего собственного кастома <document> ? Приветствуются любые предложения/реквесты!

Хотелось бы в конечном итоге включить этот пример hello world в папку «Следующие примеры» и на домашнюю страницу Apollo.

Единственным предварительным условием для рендеринга данных на сервере является то, что они возвращаются как объект в getInitialProps , нет необходимости в переопределениях.

Попался. Я думаю, что это немного сложно с react-apollo, потому что, как указал @nmaro :

вопрос заключается в том, есть ли у apollo способ обнаружить все вызовы данных, которые потребуются для рендеринга дерева компонентов, и сделать все это в одном вызове функции, который можно предоставить для getInitialProps.

Попался

@ads1018 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 Кажется простым изменением выставить app в дополнение к renderPage , но есть ли что-то, что я упускаю из виду?

@bs1180 ах, гениально. Это то, что я искал. Надеюсь, это простое изменение, чтобы выставить app . Это мгновенно сделало бы Next удобной для клиента инфраструктурой GraphQL.

@ bs1180 Я открыл app внутри возвращаемого объекта renderPage . Совпадает ли это с тем, что вы думали?

@ ads1018 Не совсем так - в вашей версии render все еще вызывается, что было бы ненужным дублированием, если renderToStringWithData будет вызываться вручную.

Я еще немного поработал над этим, и мой конечный результат далеко не так красив, как я сначала себе представлял, в первую очередь потому, что основное приложение отображается как дочерний элемент компонента <Main /> (в __next div), который дует избегайте передачи любого контекста вашему приложению сверху. Таким образом, для повторного добавления контекста Apollo по-прежнему требуется HOC.

@ bs1180 Понятно . Можно ли отобразить <Main /> как дочерний элемент ApolloProvider , чтобы мы могли передать контекст?

Я не уверен, что вы имеете в виду, но я думаю, что это неправильное направление. Идеального SSR можно достичь с помощью всего лишь HOC — вот моя сборная версия в качестве отправной точки:

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

initClient и initStore смоделированы на примере редукса. Тогда каждая страница выглядит так:

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

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

Надеюсь, что это полезно - я хотел бы знать, есть ли другие возможности для расследования или что-то, что я упускаю из виду.

@ bs1180 Круто, это очень полезно, спасибо, что поделились.

Есть ли что-нибудь еще, что мы можем сделать для отображения страниц с данными graphql внутри _document.js ? Было бы неплохо, если бы мы могли обойти этот HOC все вместе, как вы изначально предлагали.

Я так не думаю - из того, что я вижу, рендеринг на стороне клиента удалит все, что передается в контексте (будь то клиент Apollo, стандартный магазин Redux, темы и т. д.) из пользовательского _document.js . Хотя часть логики Apollo SSR может быть перемещена туда, какой-то компонент HOC/обертки все равно будет необходим для добавления необходимых объектов обратно в контекст.
Тем не менее, у кого-то, кто лучше разбирается во внутреннем устройстве next.js, может быть лучшая идея.

Что ж, если вам удастся получить рабочий пример, я хотел бы проверить его. Я все еще борюсь, чтобы заставить это работать.

У меня есть рабочий пример React Apollo и Next 😄 🚀 Надеюсь, многим из вас он будет полезен. Вы можете проверить это здесь: https://github.com/ads1018/next-apollo-example (я также развернул демонстрацию с помощью Now.)

В итоге я использовал HOC внутри своей страницы под названием withData() , который обертывает страницу ApolloProvider . Сначала я отказался от использования провайдеров постранично, а не один раз внутри одного файла, но некоторые действительно умные люди убедили меня, что это лучше для удобочитаемости и масштабируемости. Я на самом деле думаю, что withData(MyComponent) выглядит довольно красиво и предоставляет читателю хороший контекст (без каламбура), что конкретная страница извлекает данные.

Спасибо @bs1180 и @rauchg за то, что указали мне правильное направление. Если вы хотите добавить пример 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 . Я полагаю, что в полномасштабном приложении/веб-сайте с сотнями узлов и большим количеством поступающих данных двукратный рендеринг на каждом маршруте может привести к некоторой задержке.

Проект, над которым я работаю, представляет собой крупномасштабный редакционный сайт, поэтому я надеюсь провести сравнительный анализ различных подходов, включая ваш. Хотел бы обсудить больше в твиттере , если хотите. Спасибо за вашу работу!

@estrattonbailey Попался. Я предполагаю, что это будет масштабироваться очень хорошо. При начальной загрузке страницы getInitialProps будет выполняться только на сервере. Вы правы, что getInitialProps будет снова выполнено на клиенте, но никакие данные не будут запрашиваться дважды, потому что getDataFromTree заключено в условие, которое проверяет, находимся ли мы на сервере или нет.

Дополнительное примечание: если вас беспокоит начальное время загрузки страницы из-за того, что на странице запрашивается множество компонентов и данных, вы всегда можете указать apollo намеренно пропускать определенные запросы во время SSR и разгружать их клиенту, передав ssr: false в параметрах запроса apollo.

Я свяжусь с вами в твиттере , если вы хотите обсудить дальше :)

Вы правы, что getInitialProps будет снова выполняться на клиенте, но никакие данные не будут запрашиваться дважды, потому что getDataFromTree заключен в условное выражение, которое проверяет, находимся ли мы на сервере или нет.

Важно помнить, что getInitialProps выполняется на стороне клиента только при переходе с помощью <Link> , а не после начальной загрузки.

@ads1018 @estrattonbailey AFAIK действительно есть 2 рендеринга на стороне сервера при загрузке первой страницы: getDataFromTree выполняется и рендерит все дерево внутри, затем рендеринг вызывается снова для создания ответа HTML. Не думайте, есть ли способ избежать этого, но я думаю, что он все еще довольно эффективен благодаря сетевым обходам, которых избегает SSR.

Я предполагаю, что производительность максимальна, когда сервер GraphQL размещен на том же компьютере, что и сервер Next.js, поэтому вы всегда можете попробовать это, если вас беспокоит производительность (на данный момент я прототипирую свое приложение с Graphcool для backend, а Next.js развернут с помощью Now/Zeit World).

@sedubois @estrattonbailey Поправьте меня, если я ошибаюсь, но мы по-прежнему рендерим только один раз . getDataFromTree не отображает дерево, он просто возвращает промис, который разрешается, когда данные готовы в нашем хранилище клиента Apollo. В момент, когда обещание разрешается, наше хранилище клиентов Apollo будет полностью инициализировано, и мы можем опционально отображать дерево, если мы хотим передать строковые результаты в ответе на HTTP-запрос, но в моем примере мы этого не делаем.

getDataFromTree не отображает дерево

@ads1018 ads1018 AFAIK и, глядя на код Apollo , он _does_ рекурсивно отображает дерево (просто для того, чтобы вызвать выборку данных Apollo). Итак, 2 рендеринга на стороне сервера при загрузке первой страницы.

Но в любом случае, благодаря вашей демонстрации, теперь у нас есть полезная интеграция между Apollo и Next, и оставшиеся вопросы о производительности Apollo SSR, я думаю, больше не имеют ничего конкретного для Next.js. Предлагаю задать вопросы об этом там.

@sedubois , что такое рендер? Я бы назвал это ходьбой и встряхиванием дерева. Кажется, оптимизирован довольно хорошо — подавлен setState и нет согласования с DOM.

@ads1018 хороший пример! Похоже, что это было добавлено и в Wiki здесь , так что этот вопрос, вероятно, может быть закрыт?

копия @rauchg

Мы должны получить сообщение в блоге об Apollo + Next.js в блоге Apollo!

Пример @stubailo @ads1018 великолепен 👏 Для чего-то большего, использующего те же принципы Apollo, вы можете проверить мое приложение: https://github.com/relatenow/relate

Спасибо @helper. Я в восторге от того, как это вышло. Я чувствую, что открыл святой Грааль разработки приложений с помощью Next.js + Apollo. Я собирался опубликовать пост в блоге, чтобы распространять Евангелие, но так и не дошел до этого. @stubailo Я был бы рад сотрудничать в публикации для среды Apollo :)

Большое спасибо @sedubois за помощь с примером и его милым приложением. 😄

@ads1018 хотел бы видеть вас в блоге. Когда будете готовы поговорить об этом, отправьте мне (тее) сообщение в Apollo Slack. :)

@helper Вы совершенно правы. Я должен сделать новый пропуск проблемы, чтобы увидеть, можно ли закрыть проблемы 😄

@stubailo @theadactyl классная идея ❤️

Кто-нибудь знает о проблеме / PR для просмотра - относительно запроса данных дважды на стороне сервера? Просто любопытно

Была ли эта страница полезной?
0 / 5 - 0 рейтинги